Add support for static variables in GDScript

Which allows editable data associated with a particular class instead of
the instance. Scripts with static variables are kept in memory
indefinitely unless the `@static_unload` annotation is used or the
`static_unload()` method is called on the GDScript.

If the custom function `_static_init()` exists it will be called when
the class is loaded, after the static variables are set.
This commit is contained in:
George Marques 2023-04-19 11:10:35 -03:00
parent 352ebe9725
commit 0ba6048ad3
No known key found for this signature in database
GPG key ID: 046BD46A3201E43D
36 changed files with 689 additions and 86 deletions

View file

@ -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<StringName, Ref<GDScript>> &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<StringName, Ref<GDScript>> &inner : subclasses) {
inner.value->_save_old_static_data();
}
}
void GDScript::_restore_old_static_data() {
for (KeyValue<StringName, MemberInfo> &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<StringName, Ref<GDScript>> &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<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
if (E) {
if (E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(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<StringName, MemberInfo>::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<Script>();
}
for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
static_variables.clear();
static_variables_indices.clear();
if (implicit_initializer) {
clear_data->functions.insert(implicit_initializer);
implicit_initializer = nullptr;
@ -1303,6 +1416,11 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
implicit_ready = nullptr;
}
if (static_initializer) {
clear_data->functions.insert(static_initializer);
static_initializer = nullptr;
}
_save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
@ -1357,10 +1475,6 @@ GDScript::~GDScript() {
GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
}
if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
GDScriptCache::remove_script(get_path());
}
}
//////////////////////////////
@ -2391,6 +2505,7 @@ GDScriptLanguage::GDScriptLanguage() {
ERR_FAIL_COND(singleton);
singleton = this;
strings._init = StaticCString::create("_init");
strings._static_init = StaticCString::create("_static_init");
strings._notification = StaticCString::create("_notification");
strings._set = StaticCString::create("_set");
strings._get = StaticCString::create("_get");