diff --git a/core/object/object.cpp b/core/object/object.cpp index b3a4ec6e2ed6..0649fd903287 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1075,17 +1075,18 @@ void Object::add_user_signal(const MethodInfo &p_signal) { ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), "Trying to add already existing signal '" + p_signal.name + "'."); SignalData s; s.user = p_signal; + s.removable = true; signal_map[p_signal.name] = s; } -bool Object::_has_user_signal(const StringName &p_name) const { +bool Object::has_user_signal(const StringName &p_name) const { if (!signal_map.has(p_name)) { return false; } return signal_map[p_name].user.name.length() > 0; } -void Object::_remove_user_signal(const StringName &p_name) { +void Object::remove_user_signal(const StringName &p_name) { SignalData *s = signal_map.getptr(p_name); ERR_FAIL_NULL_MSG(s, "Provided signal does not exist."); ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal)."); @@ -1247,10 +1248,6 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { } add_user_signal(mi); - - if (signal_map.has(p_name)) { - signal_map.getptr(p_name)->removable = true; - } } TypedArray Object::_get_signal_list() const { @@ -1301,7 +1298,7 @@ bool Object::has_signal(const StringName &p_name) const { return true; } - if (_has_user_signal(p_name)) { + if (has_user_signal(p_name)) { return true; } @@ -1672,8 +1669,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("get_meta_list"), &Object::_get_meta_list_bind); ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); - ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal); + ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::has_user_signal); + ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::remove_user_signal); { MethodInfo mi; diff --git a/core/object/object.h b/core/object/object.h index 110d2790c58e..67f2e295f213 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -634,8 +634,6 @@ class Object { mutable const StringName *_class_name_ptr = nullptr; void _add_user_signal(const String &p_name, const Array &p_args = Array()); - bool _has_user_signal(const StringName &p_name) const; - void _remove_user_signal(const StringName &p_name); Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); TypedArray _get_signal_list() const; TypedArray _get_signal_connection_list(const StringName &p_signal) const; @@ -909,6 +907,8 @@ class Object { void set_script_and_instance(const Variant &p_script, ScriptInstance *p_instance); void add_user_signal(const MethodInfo &p_signal); + bool has_user_signal(const StringName &p_name) const; + void remove_user_signal(const StringName &p_name); template Error emit_signal(const StringName &p_name, VarArgs... p_args) { diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index dff19b3a41ae..2b3ef5f10703 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -224,15 +224,7 @@ int StringName::length() const { } bool StringName::is_empty() const { - if (_data) { - if (_data->cname) { - return _data->cname[0] == 0; - } else { - return _data->name.is_empty(); - } - } - - return true; + return !_data; } StringName &StringName::operator=(const StringName &p_name) { diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 5fe47d69df67..4b45e7785119 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -81,6 +81,13 @@ Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing. + + + + + + + @@ -174,6 +181,18 @@ [b]Note:[/b] If [member ProjectSettings.editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to read converted files in an exported project. If you rely on run-time loading of files present within the PCK, set [member ProjectSettings.editor/export/convert_text_resources_to_binary] to [code]false[/code]. + + + + + Returns a [Signal] that emits when [param member_expression] variable on base is set. The member expression is not evaluated + [codeblock] + # Observe variable being set + onset(obj.x).connect(func(old, new): print("x was set, old:", old, ", new: ", new)) + [/codeblock] + [b]Note:[/b] [method onset] is a keyword, not a function. So you cannot access it as a [Callable]. + + diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index d765cfa1ea42..2b16975dec5f 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -398,8 +398,8 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l String word = str.substr(j, to - j); Color col; if (global_functions.has(word)) { - // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. - if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) { + // "assert", "preload", and "onset" are reserved, so highlight even if not followed by a bracket. + if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ONSET)) { col = global_function_color; } else { // For other global functions, check if followed by bracket. @@ -760,6 +760,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { // "assert" and "preload" are not utility functions, but are global nonetheless, so insert them. global_functions.insert(SNAME("assert")); global_functions.insert(SNAME("preload")); + global_functions.insert(SNAME("onset")); for (const StringName &E : global_function_list) { global_functions.insert(E); } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 7b9aa70686a7..8ec56d43acd0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -30,6 +30,10 @@ #include "gdscript.h" +#include "core/error/error_macros.h" +#include "core/object/ref_counted.h" +#include "core/object/script_instance.h" +#include "core/variant/callable.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" #include "gdscript_compiler.h" @@ -37,6 +41,7 @@ #include "gdscript_rpc_callable.h" #include "gdscript_tokenizer_buffer.h" #include "gdscript_warning.h" +#include "modules/gdscript/gdscript_function.h" #ifdef TOOLS_ENABLED #include "editor/gdscript_docgen.h" @@ -696,12 +701,12 @@ void GDScript::_static_default_init() { Array default_value; const GDScriptDataType &element_type = type.get_container_element_type(0); default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type); - static_variables.write[E.value.index] = default_value; + static_variables.write[E.value.index].value = default_value; } else { Variant default_value; Callable::CallError err; Variant::construct(type.builtin_type, default_value, nullptr, 0, err); - static_variables.write[E.value.index] = default_value; + static_variables.write[E.value.index].value = default_value; } } } @@ -919,6 +924,16 @@ void GDScript::unload_static() const { GDScriptCache::remove_script(fully_qualified_name); } +Signal GDScript::_get_member_set_signal(Object *p_base, MemberData &p_member, const MemberInfo &p_member_info, const StringName &p_member_name) { + if (p_member.onset_signal_name.is_empty()) { + p_member.onset_signal_name = StringName(String("@onset(") + String(p_member_name) + ")"); + if (!p_base->has_user_signal(p_member.onset_signal_name)) { + p_base->add_user_signal(MethodInfo(Variant::NIL, p_member.onset_signal_name, PropertyInfo(p_member_info.data_type.builtin_type, "old_value"), PropertyInfo(p_member_info.data_type.builtin_type, "new_value"))); + } + } + return Signal(p_base, p_member.onset_signal_name); +} + Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { @@ -963,7 +978,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } - r_ret = top->static_variables[E->value.index]; + r_ret = top->static_variables[E->value.index].value; return true; } } @@ -1003,27 +1018,8 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { GDScript *top = this; while (top) { - HashMap::ConstIterator E = top->static_variables_indices.find(p_name); - if (E) { - const MemberInfo *member = &E->value; - Variant value = p_value; - if (member->data_type.has_type && !member->data_type.is_type(value)) { - const Variant *args = &p_value; - Callable::CallError err; - Variant::construct(member->data_type.builtin_type, value, &args, 1, err); - if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { - return false; - } - } - if (likely(top->valid) && member->setter) { - const Variant *args = &value; - Callable::CallError err; - callp(member->setter, &args, 1, err); - return err.error == Callable::CallError::CALL_OK; - } else { - top->static_variables.write[member->index] = value; - return true; - } + if (HashMap::ConstIterator E = top->static_variables_indices.find(p_name)) { + return top->_set_member(nullptr, top->static_variables, E->value, p_value); } top = top->_base; @@ -1032,6 +1028,31 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { return false; } +bool GDScript::_set_member(GDScriptInstance *p_instance, Vector &p_members, const MemberInfo &p_member_info, const Variant &p_value) { + Variant value = p_value; + if (p_member_info.data_type.has_type && !p_member_info.data_type.is_type(value)) { + const Variant *args = &p_value; + Callable::CallError err; + Variant::construct(p_member_info.data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !p_member_info.data_type.is_type(value)) { + return false; + } + } + if (likely(valid) && !p_member_info.setter.is_empty()) { + const Variant *args = &value; + Callable::CallError err; + if (p_instance) { + p_instance->callp(p_member_info.setter, &args, 1, err); + } else { + callp(p_member_info.setter, &args, 1, err); + } + return err.error == Callable::CallError::CALL_OK; + } else { + _set_member_data(p_instance ? p_instance->owner : this, p_members.write[p_member_info.index], value); + return true; + } +} + void GDScript::_get_property_list(List *p_properties) const { p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); @@ -1655,61 +1676,18 @@ GDScript::~GDScript() { ////////////////////////////// bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { - { - HashMap::Iterator E = script->member_indices.find(p_name); - if (E) { - const GDScript::MemberInfo *member = &E->value; - Variant value = p_value; - if (member->data_type.has_type && !member->data_type.is_type(value)) { - const Variant *args = &p_value; - Callable::CallError err; - Variant::construct(member->data_type.builtin_type, value, &args, 1, err); - if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { - return false; - } - } - if (likely(script->valid) && member->setter) { - const Variant *args = &value; - Callable::CallError err; - callp(member->setter, &args, 1, err); - return err.error == Callable::CallError::CALL_OK; - } else { - members.write[member->index] = value; - return true; - } - } + if (HashMap::Iterator E = script->member_indices.find(p_name)) { + return script->_set_member(this, members, E->value, p_value); } GDScript *sptr = script.ptr(); while (sptr) { - { - HashMap::ConstIterator E = sptr->static_variables_indices.find(p_name); - if (E) { - const GDScript::MemberInfo *member = &E->value; - Variant value = p_value; - if (member->data_type.has_type && !member->data_type.is_type(value)) { - const Variant *args = &p_value; - Callable::CallError err; - Variant::construct(member->data_type.builtin_type, value, &args, 1, err); - if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { - return false; - } - } - if (likely(sptr->valid) && member->setter) { - const Variant *args = &value; - Callable::CallError err; - callp(member->setter, &args, 1, err); - return err.error == Callable::CallError::CALL_OK; - } else { - sptr->static_variables.write[member->index] = value; - return true; - } - } + if (HashMap::ConstIterator E = sptr->static_variables_indices.find(p_name)) { + return sptr->_set_member(nullptr, sptr->static_variables, E->value, p_value); } if (likely(sptr->valid)) { - HashMap::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); - if (E) { + if (HashMap::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set)) { Variant name = p_name; const Variant *args[2] = { &name, &p_value }; @@ -1737,7 +1715,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } - r_ret = members[E->value.index]; + r_ret = members[E->value.index].value; return true; } } @@ -1761,7 +1739,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } - r_ret = sptr->static_variables[E->value.index]; + r_ret = sptr->static_variables[E->value.index].value; return true; } } @@ -2118,14 +2096,13 @@ const Variant GDScriptInstance::get_rpc_config() const { void GDScriptInstance::reload_members() { #ifdef DEBUG_ENABLED - Vector new_members; + Vector new_members; new_members.resize(script->member_indices.size()); //pass the values to the new indices for (KeyValue &E : script->member_indices) { if (member_indices_cache.has(E.key)) { - Variant value = members[member_indices_cache[E.key]]; - new_members.write[E.value.index] = value; + new_members.write[E.value.index] = members[member_indices_cache[E.key]]; } } @@ -2768,6 +2745,7 @@ void GDScriptLanguage::get_reserved_words(List *p_words) const { // Functions (highlighter uses global function color instead). "assert", "preload", + "onset", // Types (highlighter uses type color instead). "void", nullptr, diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 9bb39aac0f7e..709531ad00a8 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -72,6 +72,11 @@ class GDScript : public Script { PropertyInfo property_info; }; + struct MemberData { + Variant value; + StringName onset_signal_name; + }; + struct ClearData { RBSet functions; RBSet> scripts; @@ -102,7 +107,7 @@ class GDScript : public Script { // Only static variables of the current class. HashMap static_variables_indices; - Vector static_variables; // Static variable values. + Vector static_variables; // Static variable values. HashMap constants; HashMap member_functions; @@ -143,7 +148,7 @@ class GDScript : public Script { #ifdef TOOLS_ENABLED // For static data storage during hot-reloading. HashMap old_static_variables_indices; - Vector old_static_variables; + Vector old_static_variables; void _save_old_static_data(); void _restore_old_static_data(); @@ -216,6 +221,17 @@ class GDScript : public Script { GDScript *_get_gdscript_from_variant(const Variant &p_variant); void _collect_function_dependencies(GDScriptFunction *p_func, RBSet &p_dependencies, const GDScript *p_except); void _collect_dependencies(RBSet &p_dependencies, const GDScript *p_except); + static Signal _get_member_set_signal(Object *p_base, MemberData &p_member, const MemberInfo &p_member_info, const StringName &p_member_name); + _FORCE_INLINE_ bool _set_member(GDScriptInstance *p_instance, Vector &p_members, const MemberInfo &p_member_info, const Variant &p_value); + _FORCE_INLINE_ static void _set_member_data(Object *p_emitter, MemberData &p_member_data, const Variant &p_value) { + if (!p_member_data.onset_signal_name.is_empty()) { + Variant old_value = p_member_data.value; + p_member_data.value = p_value; + p_emitter->emit_signal(p_member_data.onset_signal_name, old_value, p_member_data.value); + } else { + p_member_data.value = p_value; + } + } protected: bool _get(const StringName &p_name, Variant &r_ret) const; @@ -361,7 +377,7 @@ class GDScriptInstance : public ScriptInstance { #ifdef DEBUG_ENABLED HashMap member_indices_cache; //used only for hot script reloading #endif - Vector members; + Vector members; bool base_ref_counted; SelfList::List pending_func_states; @@ -387,7 +403,7 @@ class GDScriptInstance : public ScriptInstance { virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); - Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } + Variant debug_get_member_by_index(int p_idx) const { return members[p_idx].value; } virtual void notification(int p_notification, bool p_reversed = false); String to_string(bool *r_valid); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7f0d5005cb0f..88412d90db2c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -30,6 +30,9 @@ #include "gdscript_analyzer.h" +#include "core/error/error_macros.h" +#include "core/object/object.h" +#include "core/variant/variant.h" #include "gdscript.h" #include "gdscript_utility_callable.h" #include "gdscript_utility_functions.h" @@ -42,6 +45,7 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" #include "core/templates/hash_map.h" +#include "modules/gdscript/gdscript_parser.h" #include "scene/resources/packed_scene.h" #if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED) @@ -1562,6 +1566,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::IDENTIFIER: case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::LITERAL: + case GDScriptParser::Node::ONSET: case GDScriptParser::Node::PRELOAD: case GDScriptParser::Node::SELF: case GDScriptParser::Node::SUBSCRIPT: @@ -2544,6 +2549,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::LITERAL: reduce_literal(static_cast(p_expression)); break; + case GDScriptParser::Node::ONSET: + reduce_onset(static_cast(p_expression)); + break; case GDScriptParser::Node::PRELOAD: reduce_preload(static_cast(p_expression)); break; @@ -4633,6 +4641,69 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { ensure_cached_external_parser_for_class(p_preload->get_datatype().class_type, nullptr, "Trying to resolve preload", p_preload); } +void GDScriptAnalyzer::reduce_onset(GDScriptParser::OnSetNode *p_onset) { + ERR_FAIL_NULL(p_onset); + ERR_FAIL_NULL(p_onset->member_expr); + + reduce_expression(p_onset->member_expr); + if (p_onset->member_expr->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript = static_cast(p_onset->member_expr); + ERR_FAIL_NULL(subscript->base); + + GDScriptParser::DataType base_type = subscript->base->get_datatype(); + p_onset->base = subscript->base; + + if (subscript->is_attribute) { + ERR_FAIL_NULL(subscript->attribute); + p_onset->member_name = subscript->attribute->name; + p_onset->is_member_name_dynamic = false; + } else { + ERR_FAIL_NULL(subscript->index); + if (subscript->index->is_constant) { + if (subscript->index->reduced_value.is_string()) { + p_onset->member_name = subscript->index->reduced_value; + p_onset->is_member_name_dynamic = false; + } else { + push_error(R"(Member expression name be a String or StringName.)", subscript->index); + } + } else { + p_onset->member_name_expr = subscript->index; + p_onset->is_member_name_dynamic = true; + } + } + } else if (p_onset->member_expr->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *identifier = static_cast(p_onset->member_expr); + p_onset->member_name = identifier->name; + p_onset->is_member_name_dynamic = false; + } else { + push_error(R"(Member expression must be a subscript or an identifier.)", p_onset->member_expr); + } + + if (p_onset->base) { + GDScriptParser::DataType base_type = p_onset->base->get_datatype(); + if (base_type.is_hard_type() && !base_type.is_variant() && base_type.kind == GDScriptParser::DataType::NATIVE) { + if (base_type.kind != GDScriptParser::DataType::CLASS) { + push_error(R"(Member expression base must be a GDScript or instance of GDScript.)", p_onset->base); + } + } else { + mark_node_unsafe(p_onset->member_expr); + } + } + + if (p_onset->is_member_name_dynamic) { + GDScriptParser::DataType name_type = p_onset->member_name_expr->get_datatype(); + if (name_type.is_hard_type() && !name_type.is_variant()) { + if (name_type.kind != GDScriptParser::DataType::BUILTIN || (name_type.builtin_type != Variant::STRING && name_type.builtin_type != Variant::STRING_NAME)) { + push_error(R"(Member expression name must be a String or StringName.)", p_onset->base); + } + } else { + mark_node_unsafe(p_onset->member_expr); + } + } + + p_onset->set_datatype(make_signal_type(MethodInfo(Variant::NIL, "", PropertyInfo(Variant::NIL, "old_value"), PropertyInfo(Variant::NIL, "new_value")))); +} + void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { p_self->is_constant = false; p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype())); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 3b781409a4df..7eabda394f46 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -112,6 +112,7 @@ class GDScriptAnalyzer { void reduce_lambda(GDScriptParser::LambdaNode *p_lambda); void reduce_literal(GDScriptParser::LiteralNode *p_literal); void reduce_preload(GDScriptParser::PreloadNode *p_preload); + void reduce_onset(GDScriptParser::OnSetNode *p_onset); void reduce_self(GDScriptParser::SelfNode *p_self); void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type = false); void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index f4f445e09667..173e2d6e9e2a 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -752,6 +752,41 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return result; } break; + case GDScriptParser::Node::ONSET: { + const GDScriptParser::OnSetNode *onset = static_cast(p_expression); + + Vector args; + if (onset->base) { + GDScriptCodeGenerator::Address base_addr = _parse_expression(codegen, r_error, onset->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + args.push_back(base_addr); + } else { + args.push_back(GDScriptCodeGenerator::Address(codegen.is_static ? GDScriptCodeGenerator::Address::CLASS : GDScriptCodeGenerator::Address::SELF)); + } + + if (onset->is_member_name_dynamic) { + GDScriptCodeGenerator::Address member_name_addr = _parse_expression(codegen, r_error, onset->member_name_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + args.push_back(member_name_addr); + } else { + args.push_back(codegen.add_constant(onset->member_name)); + } + + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(onset->get_datatype(), codegen.script)); + gen->write_call_gdscript_utility(result, "get_member_set_signal", args); + + for (int i = 0; i < args.size(); i++) { + if (args[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + + return result; + } break; case GDScriptParser::Node::PRELOAD: { const GDScriptParser::PreloadNode *preload = static_cast(p_expression); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 73f2b1d61833..9c048422f378 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -459,6 +459,13 @@ void GDScriptLanguage::get_public_functions(List *p_functions) const mi.default_arguments.push_back(String()); p_functions->push_back(mi); } + { + MethodInfo mi; + mi.name = "onset"; + mi.return_val.type = Variant::SIGNAL; + mi.arguments.push_back(PropertyInfo(Variant::NIL, "member_expression")); + p_functions->push_back(mi); + } } void GDScriptLanguage::get_public_constants(List> *p_constants) const { @@ -1448,7 +1455,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } static const char *_keywords_with_args[] = { - "assert", "preload", + "assert", "preload", "onset", nullptr }; @@ -3833,9 +3840,9 @@ ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symb // Allows class functions with the names like built-ins to be handled properly. if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { - // Need special checks for assert and preload as they are technically + // Need special checks for assert, preload, and onset as they are technically // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol || "onset" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.class_name = "@GDScript"; r_result.class_member = p_symbol; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e169566705a6..8f6195bfc4aa 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1917,6 +1917,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: // Fine. break; + case Node::ONSET: + // `onset` is a function-like keyword. + push_warning(expression, GDScriptWarning::RETURN_VALUE_DISCARDED, "onset"); + break; case Node::PRELOAD: // `preload` is a function-like keyword. push_warning(expression, GDScriptWarning::RETURN_VALUE_DISCARDED, "preload"); @@ -3400,6 +3404,30 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ return preload; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_onset(ExpressionNode *p_previous_operand, bool p_can_assign) { + OnSetNode *onset = alloc_node(); + + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "onset".)"); + + make_completion_context(COMPLETION_RESOURCE_PATH, onset); + push_completion_call(onset); + + onset->member_expr = parse_expression(false); + + if (onset->member_expr == nullptr) { + push_error(R"(Expected member expression after "(".)"); + } + + pop_completion_call(); + + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after member expression.)*"); + complete_extents(onset); + + return onset; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) { LambdaNode *lambda = alloc_node(); lambda->parent_function = current_function; @@ -3988,6 +4016,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS, { nullptr, nullptr, PREC_NONE }, // NAMESPACE, + { &GDScriptParser::parse_onset, nullptr, PREC_NONE }, // ONSET, { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF, { nullptr, nullptr, PREC_NONE }, // SIGNAL, diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7f64ae902b03..04aa28795123 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -317,6 +317,7 @@ class GDScriptParser { LITERAL, MATCH, MATCH_BRANCH, + ONSET, PARAMETER, PASS, PATTERN, @@ -972,6 +973,19 @@ class GDScriptParser { } }; + struct OnSetNode : public ExpressionNode { + ExpressionNode *member_expr = nullptr; + ExpressionNode *base = nullptr; + ExpressionNode *member_name_expr = nullptr; + StringName member_name; + + bool is_member_name_dynamic = false; + + OnSetNode() { + type = ONSET; + } + }; + struct ParameterNode : public AssignableNode { ParameterNode() { type = PARAMETER; @@ -1549,6 +1563,7 @@ class GDScriptParser { ExpressionNode *parse_call(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_onset(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 404b61fb400c..7ef63fc77b63 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -114,6 +114,7 @@ static const char *token_names[] = { "in", // IN, "is", // IS, "namespace", // NAMESPACE + "onset", // ONSET, "preload", // PRELOAD, "self", // SELF, "signal", // SIGNAL, @@ -516,6 +517,7 @@ GDScriptTokenizer::Token GDScriptTokenizerText::annotation() { KEYWORD("namespace", Token::NAMESPACE) \ KEYWORD("not", Token::NOT) \ KEYWORD_GROUP('o') \ + KEYWORD("onset", Token::ONSET) \ KEYWORD("or", Token::OR) \ KEYWORD_GROUP('p') \ KEYWORD("pass", Token::PASS) \ diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 5d7637517381..0015886440df 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -126,6 +126,7 @@ class GDScriptTokenizer { IN, IS, NAMESPACE, + ONSET, PRELOAD, SELF, SIGNAL, diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 59dd983ed25a..c08fba0e5ffb 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -303,7 +303,7 @@ struct GDScriptUtilityFunctionsDefinitions { for (const KeyValue &E : base->member_indices) { if (!d.has(E.key)) { - d[E.key] = ins->members[E.value.index]; + d[E.key] = ins->members[E.value.index].value; } } *r_ret = d; @@ -382,7 +382,7 @@ struct GDScriptUtilityFunctionsDefinitions { for (KeyValue &E : gd_ref->member_indices) { if (d.has(E.key)) { - ins->members.write[E.value.index] = d[E.key]; + ins->members.write[E.value.index].value = d[E.key]; } } } @@ -607,6 +607,91 @@ struct GDScriptUtilityFunctionsDefinitions { r_error.argument = 1; r_error.expected = Variant::NIL; } + + static inline void get_member_set_signal(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(2); + +#define FAIL_INVALID_BASE_ERROR \ + *r_ret = RTR("Invalid base for member set signal, should be a GDScript or instance of GDScript."); \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = 0; \ + r_error.expected = Variant::OBJECT; \ + return; + + if (!p_args[1]->is_string()) { + *r_ret = RTR("Invalid member name for member set signal, should be a String or StringName."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + + if (p_args[0]->get_type() != Variant::OBJECT) { + FAIL_INVALID_BASE_ERROR + } + + bool was_type_freed = false; + Object *base = p_args[0]->get_validated_object_with_check(was_type_freed); + if (was_type_freed) { + *r_ret = RTR("Base is a previously freed instance."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + + if (!base) { + FAIL_INVALID_BASE_ERROR + } + + StringName member_name = p_args[1]->operator StringName(); + ScriptInstance *inst = base->get_script_instance(); + if (inst && inst->get_language() == GDScriptLanguage::get_singleton()) { + GDScriptInstance *gdinst = static_cast(inst); + if (HashMap::ConstIterator E = gdinst->script->member_indices.find(member_name)) { + *r_ret = GDScript::_get_member_set_signal(base, gdinst->members.write[E->value.index], E->value, member_name); + return; + } + + GDScript *script = gdinst->script.ptr(); + while (script) { + if (HashMap::ConstIterator E = script->static_variables_indices.find(member_name)) { + *r_ret = GDScript::_get_member_set_signal(script, script->static_variables.write[E->value.index], E->value, member_name); + return; + } + + script = script->_base; + } + + *r_ret = RTR(vformat("Could not find member %s on base %s.", member_name, base)); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + + GDScript *script = Object::cast_to(base); + if (script) { + while (script) { + if (HashMap::ConstIterator E = script->static_variables_indices.find(member_name)) { + *r_ret = GDScript::_get_member_set_signal(script, script->static_variables.write[E->value.index], E->value, member_name); + return; + } + + script = script->_base; + } + + *r_ret = RTR(vformat("Could not find member %s on base %s.", member_name, base)); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + + FAIL_INVALID_BASE_ERROR + +#undef FAIL_INVALID_BASE_ERROR + } }; struct GDScriptUtilityFunctionInfo { @@ -725,6 +810,7 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY); REGISTER_FUNC(len, true, Variant::INT, VARARG("var")); REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type")); + REGISTER_FUNC(get_member_set_signal, false, Variant::SIGNAL, ARG("base", Variant::OBJECT), ARG("member_name", Variant::STRING_NAME)); } void GDScriptUtilityFunctions::unregister_functions() { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 4617a0dbb9f4..6260227a9efe 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -392,10 +392,6 @@ void (*type_init_function_table[])(Variant *) = { #define OPCODE(m_op) \ m_op: #define OPCODE_WHILE(m_test) -#define OPCODES_END \ - OPSEXIT: -#define OPCODES_OUT \ - OPSOUT: #define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test]; #ifdef DEBUG_ENABLED #define DISPATCH_OPCODE \ @@ -404,14 +400,10 @@ void (*type_init_function_table[])(Variant *) = { #else #define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]] #endif -#define OPCODE_BREAK goto OPSEXIT -#define OPCODE_OUT goto OPSOUT #else #define OPCODES_TABLE #define OPCODE(m_op) case m_op: #define OPCODE_WHILE(m_test) while (m_test) -#define OPCODES_END -#define OPCODES_OUT #define DISPATCH_OPCODE continue #ifdef _MSC_VER #define OPCODE_SWITCH(m_test) \ @@ -420,10 +412,15 @@ void (*type_init_function_table[])(Variant *) = { #else #define OPCODE_SWITCH(m_test) switch (m_test) #endif -#define OPCODE_BREAK break -#define OPCODE_OUT break #endif +#define OPCODES_END \ + OPSEXIT: +#define OPCODES_OUT \ + OPSOUT: +#define OPCODE_BREAK goto OPSEXIT +#define OPCODE_OUT goto OPSOUT + // Helpers for VariantInternal methods in macros. #define OP_GET_BOOL get_bool #define OP_GET_INT get_int @@ -645,10 +642,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a { \ int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ int address_type = (address & ADDR_TYPE_MASK) >> ADDR_BITS; \ - if (unlikely(address_type < 0 || address_type >= ADDR_TYPE_MAX)) { \ - err_text = "Bad address type."; \ - OPCODE_BREAK; \ - } \ int address_index = address & ADDR_MASK; \ if (unlikely(address_index < 0 || address_index >= variant_address_limits[address_type])) { \ if (address_type == ADDR_TYPE_MEMBER && !p_instance) { \ @@ -658,22 +651,113 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } \ OPCODE_BREAK; \ } \ - m_v = &variant_addresses[address_type][address_index]; \ + switch (address_type) { \ + case ADDR_TYPE_STACK: \ + m_v = &stack[address_index]; \ + break; \ + case ADDR_TYPE_CONSTANT: \ + m_v = &_constants_ptr[address_index]; \ + break; \ + case ADDR_TYPE_MEMBER: \ + m_v = &p_instance->members.write[address_index].value; \ + break; \ + default: \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ if (unlikely(!m_v)) \ OPCODE_BREAK; \ } +#define SET_VARIANT(m_code_ofs, m_val) \ + { \ + int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ + int address_type = (address & ADDR_TYPE_MASK) >> ADDR_BITS; \ + if (unlikely(address_type < 0 || address_type >= ADDR_TYPE_MAX)) { \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ + int address_index = address & ADDR_MASK; \ + if (unlikely(address_index < 0 || address_index >= variant_address_limits[address_type])) { \ + if (address_type == ADDR_TYPE_MEMBER && !p_instance) { \ + err_text = "Cannot access member without instance."; \ + } else { \ + err_text = "Bad address index."; \ + } \ + OPCODE_BREAK; \ + } \ + switch (address_type) { \ + case ADDR_TYPE_STACK: \ + stack[address_index] = m_val; \ + break; \ + case ADDR_TYPE_CONSTANT: \ + _constants_ptr[address_index] = m_val; \ + break; \ + case ADDR_TYPE_MEMBER: \ + if (!p_instance) { \ + err_text = "Cannot access member without instance."; \ + OPCODE_BREAK; \ + } \ + GDScript::_set_member_data(p_instance->owner, p_instance->members.write[address_index], m_val); \ + break; \ + default: \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ + } + #else #define GD_ERR_BREAK(m_cond) #define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - { \ - int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ - m_v = &variant_addresses[(address & ADDR_TYPE_MASK) >> ADDR_BITS][address & ADDR_MASK]; \ - if (unlikely(!m_v)) \ - OPCODE_BREAK; \ +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + { \ + int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ + int address_type = (address & ADDR_TYPE_MASK) >> ADDR_BITS; \ + int address_index = address & ADDR_MASK; \ + switch (address_type) { \ + case ADDR_TYPE_STACK: \ + m_v = &stack[address_index]; \ + break; \ + case ADDR_TYPE_CONSTANT: \ + m_v = &_constants_ptr[address_index]; \ + break; \ + case ADDR_TYPE_MEMBER: \ + m_v = &p_instance->members.write[address_index].value; \ + break; \ + default: \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ + if (unlikely(!m_v)) { \ + OPCODE_BREAK; \ + } \ + } + +#define SET_VARIANT(m_code_ofs, m_val) \ + { \ + int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ + int address_type = (address & ADDR_TYPE_MASK) >> ADDR_BITS; \ + int address_index = address & ADDR_MASK; \ + switch (address_type) { \ + case ADDR_TYPE_STACK: \ + stack[address_index] = m_val; \ + break; \ + case ADDR_TYPE_CONSTANT: \ + _constants_ptr[address_index] = m_val; \ + break; \ + case ADDR_TYPE_MEMBER: \ + if (!p_instance) { \ + err_text = "Cannot access member without instance."; \ + OPCODE_BREAK; \ + } \ + GDScript::_set_member_data(p_instance->owner, p_instance->members.write[address_index], m_val); \ + break; \ + default: \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ } #endif @@ -704,8 +788,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? (int)p_instance->members.size() : 0 }; #endif - Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; - #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { int last_opcode = _code_ptr[ip]; @@ -1313,7 +1395,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int index = _code_ptr[ip + 3]; GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size()); - gdscript->static_variables.write[index] = *value; + GDScript::_set_member_data(gdscript, gdscript->static_variables.write[index], *value); ip += 4; } @@ -1331,7 +1413,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int index = _code_ptr[ip + 3]; GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size()); - *target = gdscript->static_variables[index]; + *target = gdscript->static_variables[index].value; ip += 4; } @@ -1339,10 +1421,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN) { CHECK_SPACE(3); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); - *dst = *src; + SET_VARIANT(0, *src); ip += 3; } @@ -1350,9 +1431,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_NULL) { CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 0); - *dst = Variant(); + SET_VARIANT(0, Variant()); ip += 2; } @@ -1360,9 +1440,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TRUE) { CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 0); - *dst = true; + SET_VARIANT(0, true); ip += 2; } @@ -1370,9 +1449,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_FALSE) { CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 0); - *dst = false; + SET_VARIANT(0, false); ip += 2; } @@ -1380,7 +1458,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; @@ -1390,8 +1467,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (Variant::can_convert_strict(src->get_type(), var_type)) { #endif // DEBUG_ENABLED + Variant tmp; Callable::CallError ce; - Variant::construct(var_type, *dst, const_cast(&src), 1, ce); + Variant::construct(var_type, tmp, const_cast(&src), 1, ce); + SET_VARIANT(0, tmp); } else { #ifdef DEBUG_ENABLED err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + @@ -1400,7 +1479,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } else { #endif // DEBUG_ENABLED - *dst = *src; + SET_VARIANT(0, *src); } ip += 4; @@ -1409,7 +1488,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { CHECK_SPACE(6); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); GET_VARIANT_PTR(script_type, 2); @@ -1436,7 +1514,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } - *dst = *src; + SET_VARIANT(0, *src); ip += 6; } @@ -1444,7 +1522,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) { CHECK_SPACE(9); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); GET_VARIANT_PTR(key_script_type, 2); @@ -1480,7 +1557,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } - *dst = *src; + SET_VARIANT(0, *src); ip += 9; } @@ -1488,7 +1565,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); #ifdef DEBUG_ENABLED @@ -1516,7 +1592,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } #endif // DEBUG_ENABLED - *dst = *src; + SET_VARIANT(0, *src); ip += 4; } @@ -1524,7 +1600,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); #ifdef DEBUG_ENABLED @@ -1574,7 +1649,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif // DEBUG_ENABLED - *dst = *src; + SET_VARIANT(0, *src); ip += 4; }