You've already forked godot
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:
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user