diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 4a6368ca6c7..949253c9989 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -456,6 +456,9 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await. + + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@static_unload[/code] annotation is used in a script without any static variables. + When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs. diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 08a48830545..d8f12f72329 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -622,6 +622,12 @@ [/codeblock] + + + + Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values. + + diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index e74c2866e63..3bc11c69e9d 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -651,6 +651,49 @@ String GDScript::_get_debug_path() const { } } +Error GDScript::_static_init() { + if (static_initializer) { + Callable::CallError call_err; + static_initializer->call(nullptr, nullptr, 0, call_err); + if (call_err.error != Callable::CallError::CALL_OK) { + return ERR_CANT_CREATE; + } + } + Error err = OK; + for (KeyValue> &inner : subclasses) { + err = inner.value->_static_init(); + if (err) { + break; + } + } + return err; +} + +#ifdef TOOLS_ENABLED + +void GDScript::_save_old_static_data() { + old_static_variables_indices = static_variables_indices; + old_static_variables = static_variables; + for (KeyValue> &inner : subclasses) { + inner.value->_save_old_static_data(); + } +} + +void GDScript::_restore_old_static_data() { + for (KeyValue &E : old_static_variables_indices) { + if (static_variables_indices.has(E.key)) { + static_variables.write[static_variables_indices[E.key].index] = old_static_variables[E.value.index]; + } + } + old_static_variables_indices.clear(); + old_static_variables.clear(); + for (KeyValue> &inner : subclasses) { + inner.value->_restore_old_static_data(); + } +} + +#endif + Error GDScript::reload(bool p_keep_state) { if (reloading) { return OK; @@ -696,6 +739,14 @@ Error GDScript::reload(bool p_keep_state) { } } + bool can_run = ScriptServer::is_scripting_enabled() || is_tool(); + +#ifdef DEBUG_ENABLED + if (p_keep_state && can_run && is_valid()) { + _save_old_static_data(); + } +#endif + valid = false; GDScriptParser parser; Error err = parser.parse(source, path, false); @@ -726,7 +777,7 @@ Error GDScript::reload(bool p_keep_state) { return ERR_PARSE_ERROR; } - bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); + can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); @@ -760,6 +811,19 @@ Error GDScript::reload(bool p_keep_state) { } #endif + if (can_run) { + err = _static_init(); + if (err) { + return err; + } + } + +#ifdef DEBUG_ENABLED + if (can_run && p_keep_state) { + _restore_old_static_data(); + } +#endif + reloading = false; return OK; } @@ -788,6 +852,10 @@ const Variant GDScript::get_rpc_config() const { return rpc_config; } +void GDScript::unload_static() const { + GDScriptCache::remove_script(fully_qualified_name); +} + Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { @@ -824,6 +892,19 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { return true; } } + + { + HashMap::ConstIterator E = static_variables_indices.find(p_name); + if (E) { + if (E->value.getter) { + Callable::CallError ce; + r_ret = const_cast(this)->callp(E->value.getter, nullptr, 0, ce); + return true; + } + r_ret = static_variables[E->value.index]; + return true; + } + } top = top->_base; } @@ -841,7 +922,32 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { set_source_code(p_value); reload(); } else { - return false; + const GDScript *top = this; + while (top) { + HashMap::ConstIterator E = 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 (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; + } else { + static_variables.write[member->index] = value; + return true; + } + } + top = top->_base; + } } return true; @@ -1293,6 +1399,13 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) { E.value.data_type.script_type_ref = Ref