1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-05 12:10:55 +00:00

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
36 changed files with 689 additions and 86 deletions

View File

@@ -879,6 +879,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
#endif
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
bool previous_static_context = static_context;
static_context = member.variable->is_static;
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
member.variable->set_datatype(resolving_datatype);
resolve_variable(member.variable, false);
@@ -890,6 +892,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
E->apply(parser, member.variable);
}
}
static_context = previous_static_context;
#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
@@ -897,7 +900,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
if (member.variable->initializer) {
// Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed.
// This could be improved by traversing the expression fully and checking the presence of get_node at any level.
if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
if (!member.variable->is_static && !member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
GDScriptParser::Node *expr = member.variable->initializer;
if (expr->type == GDScriptParser::Node::CAST) {
expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
@@ -1082,6 +1085,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
p_source = p_class;
}
#ifdef DEBUG_ENABLED
bool has_static_data = p_class->has_static_data;
#endif
if (!p_class->resolved_interface) {
if (!parser->has_class(p_class)) {
String script_path = p_class->get_datatype().script_path;
@@ -1124,7 +1131,29 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
for (int i = 0; i < p_class->members.size(); i++) {
resolve_class_member(p_class, i);
#ifdef DEBUG_ENABLED
if (!has_static_data) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::CLASS) {
has_static_data = member.m_class->has_static_data;
}
}
#endif
}
#ifdef DEBUG_ENABLED
if (!has_static_data && p_class->annotated_static_unload) {
GDScriptParser::Node *static_unload = nullptr;
for (GDScriptParser::AnnotationNode *node : p_class->annotations) {
if (node->name == "@static_unload") {
static_unload = node;
break;
}
}
parser->push_warning(static_unload ? static_unload : p_class, GDScriptWarning::REDUNDANT_STATIC_UNLOAD);
}
#endif
}
}
@@ -1499,6 +1528,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
GDScriptParser::FunctionNode *previous_function = parser->current_function;
parser->current_function = p_function;
bool previous_static_context = static_context;
static_context = p_function->is_static;
GDScriptParser::DataType prev_datatype = p_function->get_datatype();
@@ -1542,6 +1573,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
push_error("Constructor cannot have an explicit return type.", p_function->return_type);
}
}
} else if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._static_init) {
// Static constructor.
GDScriptParser::DataType return_type;
return_type.kind = GDScriptParser::DataType::BUILTIN;
return_type.builtin_type = Variant::NIL;
p_function->set_datatype(return_type);
if (p_function->return_type) {
GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type);
if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) {
push_error("Static constructor cannot have an explicit return type.", p_function->return_type);
}
}
} else {
if (p_function->return_type != nullptr) {
p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
@@ -1625,6 +1668,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
parser->ignored_warnings = previously_ignored_warnings;
#endif
parser->current_function = previous_function;
static_context = previous_static_context;
}
void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) {
@@ -3050,13 +3094,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
base_type.is_meta_type = false;
}
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
while (parent_function->source_lambda) {
parent_function = parent_function->source_lambda->parent_function;
if (is_self && static_context && !is_static) {
if (parser->current_function) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
while (parent_function->source_lambda) {
parent_function = parent_function->source_lambda->parent_function;
}
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
} else {
push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call);
}
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
} else if (!is_self && base_type.is_meta_type && !is_static) {
base_type.is_meta_type = false; // For `to_string()`.
push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
@@ -3073,7 +3121,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
}
if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) {
if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) {
String caller_type = String(base_type.native_type);
if (caller_type.is_empty()) {
@@ -3428,9 +3476,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
case GDScriptParser::ClassNode::Member::VARIABLE: {
if (is_base && !base.is_meta_type) {
if (is_base && (!base.is_meta_type || member.variable->is_static)) {
p_identifier->set_datatype(member.get_datatype());
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
p_identifier->variable_source = member.variable;
member.variable->usages += 1;
return;
@@ -3572,6 +3620,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
mark_lambda_use_self();
p_identifier->variable_source->usages++;
[[fallthrough]];
case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
found_source = true;
@@ -3602,13 +3651,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (found_source) {
bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
while (parent_function->source_lambda) {
parent_function = parent_function->source_lambda->parent_function;
if ((source_is_variable || source_is_signal) && static_context) {
if (parser->current_function) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
while (parent_function->source_lambda) {
parent_function = parent_function->source_lambda->parent_function;
}
push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
} else {
push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier);
}
push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
}
if (!lambda_stack.is_empty()) {