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:
parent
352ebe9725
commit
0ba6048ad3
|
@ -456,6 +456,9 @@
|
|||
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
|
||||
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.
|
||||
</member>
|
||||
<member name="debug/gdscript/warnings/redundant_static_unload" type="int" setter="" getter="" default="1">
|
||||
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.
|
||||
</member>
|
||||
<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
|
||||
When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
|
||||
</member>
|
||||
|
|
|
@ -622,6 +622,12 @@
|
|||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@static_unload">
|
||||
<return type="void" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@tool">
|
||||
<return type="void" />
|
||||
<description>
|
||||
|
|
|
@ -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,8 +922,33 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
|
|||
set_source_code(p_value);
|
||||
reload();
|
||||
} else {
|
||||
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");
|
||||
|
|
|
@ -94,6 +94,8 @@ class GDScript : public Script {
|
|||
|
||||
HashSet<StringName> members; //members are just indices to the instantiated script.
|
||||
HashMap<StringName, Variant> constants;
|
||||
HashMap<StringName, MemberInfo> static_variables_indices;
|
||||
Vector<Variant> static_variables;
|
||||
HashMap<StringName, GDScriptFunction *> member_functions;
|
||||
HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
|
||||
HashMap<StringName, Ref<GDScript>> subclasses;
|
||||
|
@ -102,6 +104,12 @@ class GDScript : public Script {
|
|||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
// For static data storage during hot-reloading.
|
||||
HashMap<StringName, MemberInfo> old_static_variables_indices;
|
||||
Vector<Variant> old_static_variables;
|
||||
void _save_old_static_data();
|
||||
void _restore_old_static_data();
|
||||
|
||||
HashMap<StringName, int> member_lines;
|
||||
HashMap<StringName, Variant> member_default_values;
|
||||
List<PropertyInfo> members_cache;
|
||||
|
@ -123,6 +131,9 @@ class GDScript : public Script {
|
|||
GDScriptFunction *implicit_initializer = nullptr;
|
||||
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
|
||||
GDScriptFunction *implicit_ready = nullptr;
|
||||
GDScriptFunction *static_initializer = nullptr;
|
||||
|
||||
Error _static_init();
|
||||
|
||||
int subclass_count = 0;
|
||||
RBSet<Object *> instances;
|
||||
|
@ -268,6 +279,8 @@ public:
|
|||
|
||||
virtual const Variant get_rpc_config() const override;
|
||||
|
||||
void unload_static() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
|
||||
#endif
|
||||
|
@ -439,6 +452,7 @@ public:
|
|||
|
||||
struct {
|
||||
StringName _init;
|
||||
StringName _static_init;
|
||||
StringName _notification;
|
||||
StringName _set;
|
||||
StringName _get;
|
||||
|
|
|
@ -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,8 +1131,30 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive) {
|
||||
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!lambda_stack.is_empty()) {
|
||||
|
|
|
@ -43,6 +43,7 @@ class GDScriptAnalyzer {
|
|||
|
||||
const GDScriptParser::EnumNode *current_enum = nullptr;
|
||||
List<GDScriptParser::LambdaNode *> lambda_stack;
|
||||
bool static_context = false;
|
||||
|
||||
// Tests for detecting invalid overloading of script members
|
||||
static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member);
|
||||
|
|
|
@ -366,6 +366,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
|
|||
return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
|
||||
case Address::CONSTANT:
|
||||
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
|
||||
case Address::STATIC_VARIABLE:
|
||||
return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS);
|
||||
case Address::LOCAL_VARIABLE:
|
||||
case Address::FUNCTION_PARAMETER:
|
||||
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
|
||||
|
|
|
@ -342,6 +342,16 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
|
|||
return err;
|
||||
}
|
||||
|
||||
void GDScriptCache::add_static_script(Ref<GDScript> p_script) {
|
||||
ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static.");
|
||||
ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static.");
|
||||
singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script;
|
||||
}
|
||||
|
||||
void GDScriptCache::remove_static_script(const String &p_fqcn) {
|
||||
singleton->static_gdscript_cache.erase(p_fqcn);
|
||||
}
|
||||
|
||||
Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
|
||||
MutexLock lock(singleton->mutex);
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ class GDScriptCache {
|
|||
HashMap<String, GDScriptParserRef *> parser_map;
|
||||
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
|
||||
HashMap<String, Ref<GDScript>> full_gdscript_cache;
|
||||
HashMap<String, Ref<GDScript>> static_gdscript_cache;
|
||||
HashMap<String, HashSet<String>> dependencies;
|
||||
HashMap<String, Ref<PackedScene>> packed_scene_cache;
|
||||
HashMap<String, HashSet<String>> packed_scene_dependencies;
|
||||
|
@ -101,6 +102,8 @@ public:
|
|||
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
|
||||
static Ref<GDScript> get_cached_script(const String &p_path);
|
||||
static Error finish_compiling(const String &p_owner);
|
||||
static void add_static_script(Ref<GDScript> p_script);
|
||||
static void remove_static_script(const String &p_fqcn);
|
||||
|
||||
static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
|
||||
static void clear_unreferenced_packed_scenes();
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
CLASS,
|
||||
MEMBER,
|
||||
CONSTANT,
|
||||
STATIC_VARIABLE,
|
||||
LOCAL_VARIABLE,
|
||||
FUNCTION_PARAMETER,
|
||||
TEMPORARY,
|
||||
|
|
|
@ -254,13 +254,29 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
|
||||
return temp;
|
||||
} else {
|
||||
// No getter or inside getter: direct member access.,
|
||||
// No getter or inside getter: direct member access.
|
||||
int idx = codegen.script->member_indices[identifier].index;
|
||||
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try static variables.
|
||||
if (codegen.script->static_variables_indices.has(identifier)) {
|
||||
if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) {
|
||||
// Perform getter.
|
||||
GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type);
|
||||
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
|
||||
Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
|
||||
gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args);
|
||||
return temp;
|
||||
} else {
|
||||
// No getter or inside getter: direct variable access.
|
||||
int idx = codegen.script->static_variables_indices[identifier].index;
|
||||
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type);
|
||||
}
|
||||
}
|
||||
|
||||
// Try class constants.
|
||||
{
|
||||
GDScript *owner = codegen.script;
|
||||
|
@ -563,7 +579,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
// Not exact arguments, but still can use method bind call.
|
||||
gen->write_call_method_bind(result, self, method, arguments);
|
||||
}
|
||||
} else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
|
||||
} else if (codegen.is_static || (codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
|
||||
GDScriptCodeGenerator::Address self;
|
||||
self.mode = GDScriptCodeGenerator::Address::CLASS;
|
||||
if (is_awaited) {
|
||||
|
@ -909,6 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
bool is_member_property = false;
|
||||
bool member_property_has_setter = false;
|
||||
bool member_property_is_in_setter = false;
|
||||
bool is_static = false;
|
||||
StringName member_property_setter_function;
|
||||
|
||||
List<const GDScriptParser::SubscriptNode *> chain;
|
||||
|
@ -925,14 +942,16 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
StringName var_name = identifier->name;
|
||||
if (_is_class_member_property(codegen, var_name)) {
|
||||
assign_class_member_property = var_name;
|
||||
} else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) {
|
||||
} else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
|
||||
is_member_property = true;
|
||||
member_property_setter_function = codegen.script->member_indices[var_name].setter;
|
||||
is_static = codegen.script->static_variables_indices.has(var_name);
|
||||
const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
|
||||
member_property_setter_function = minfo.setter;
|
||||
member_property_has_setter = member_property_setter_function != StringName();
|
||||
member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
|
||||
target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
|
||||
target_member_property.address = codegen.script->member_indices[var_name].index;
|
||||
target_member_property.type = codegen.script->member_indices[var_name].data_type;
|
||||
target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
|
||||
target_member_property.address = minfo.index;
|
||||
target_member_property.type = minfo.data_type;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1085,7 +1104,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
if (member_property_has_setter && !member_property_is_in_setter) {
|
||||
Vector<GDScriptCodeGenerator::Address> args;
|
||||
args.push_back(assigned);
|
||||
gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
|
||||
GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
|
||||
gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args);
|
||||
} else {
|
||||
gen->write_assign(target_member_property, assigned);
|
||||
}
|
||||
|
@ -1134,16 +1154,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
bool is_member = false;
|
||||
bool has_setter = false;
|
||||
bool is_in_setter = false;
|
||||
bool is_static = false;
|
||||
StringName setter_function;
|
||||
StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
|
||||
if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) {
|
||||
if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
|
||||
is_member = true;
|
||||
setter_function = codegen.script->member_indices[var_name].setter;
|
||||
is_static = codegen.script->static_variables_indices.has(var_name);
|
||||
GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
|
||||
setter_function = minfo.setter;
|
||||
has_setter = setter_function != StringName();
|
||||
is_in_setter = has_setter && setter_function == codegen.function_name;
|
||||
member.mode = GDScriptCodeGenerator::Address::MEMBER;
|
||||
member.address = codegen.script->member_indices[var_name].index;
|
||||
member.type = codegen.script->member_indices[var_name].data_type;
|
||||
member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
|
||||
member.address = minfo.index;
|
||||
member.type = minfo.data_type;
|
||||
}
|
||||
|
||||
GDScriptCodeGenerator::Address target;
|
||||
|
@ -2001,6 +2024,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
}
|
||||
|
||||
codegen.function_name = func_name;
|
||||
codegen.is_static = is_static;
|
||||
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
|
||||
|
||||
int optional_parameters = 0;
|
||||
|
@ -2024,7 +2048,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
bool is_implicit_ready = !p_func && p_for_ready;
|
||||
|
||||
if (!p_for_lambda && is_implicit_initializer) {
|
||||
// Initialize the default values for type variables before anything.
|
||||
// Initialize the default values for typed variables before anything.
|
||||
// This avoids crashes if they are accessed with validated calls before being properly initialized.
|
||||
// It may happen with out-of-order access or with `@onready` variables.
|
||||
for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
|
||||
|
@ -2033,6 +2057,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
}
|
||||
|
||||
const GDScriptParser::VariableNode *field = member.variable;
|
||||
if (field->is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
|
||||
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
|
||||
|
||||
|
@ -2056,6 +2084,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
continue;
|
||||
}
|
||||
const GDScriptParser::VariableNode *field = p_class->members[i].variable;
|
||||
if (field->is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field->onready != is_implicit_ready) {
|
||||
// Only initialize in @implicit_ready.
|
||||
continue;
|
||||
|
@ -2183,6 +2215,135 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
return gd_function;
|
||||
}
|
||||
|
||||
GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class) {
|
||||
r_error = OK;
|
||||
CodeGen codegen;
|
||||
codegen.generator = memnew(GDScriptByteCodeGenerator);
|
||||
|
||||
codegen.class_node = p_class;
|
||||
codegen.script = p_script;
|
||||
|
||||
StringName func_name = SNAME("@static_initializer");
|
||||
bool is_static = true;
|
||||
Variant rpc_config;
|
||||
GDScriptDataType return_type;
|
||||
return_type.has_type = true;
|
||||
return_type.kind = GDScriptDataType::BUILTIN;
|
||||
return_type.builtin_type = Variant::NIL;
|
||||
|
||||
codegen.function_name = func_name;
|
||||
codegen.is_static = is_static;
|
||||
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
|
||||
|
||||
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
|
||||
|
||||
// Initialize the default values for typed variables before anything.
|
||||
// This avoids crashes if they are accessed with validated calls before being properly initialized.
|
||||
// It may happen with out-of-order access or with `@onready` variables.
|
||||
for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
|
||||
if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GDScriptParser::VariableNode *field = member.variable;
|
||||
if (!field->is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
|
||||
|
||||
if (field_type.has_type) {
|
||||
codegen.generator->write_newline(field->start_line);
|
||||
|
||||
if (field_type.has_container_element_type()) {
|
||||
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
|
||||
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
|
||||
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
|
||||
codegen.generator->pop_temporary();
|
||||
|
||||
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
|
||||
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
|
||||
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
|
||||
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
// The `else` branch is for objects, in such case we leave it as `null`.
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_class->members.size(); i++) {
|
||||
// Initialize static fields.
|
||||
if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
|
||||
continue;
|
||||
}
|
||||
const GDScriptParser::VariableNode *field = p_class->members[i].variable;
|
||||
if (!field->is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
|
||||
|
||||
if (field->initializer) {
|
||||
// Emit proper line change.
|
||||
codegen.generator->write_newline(field->initializer->start_line);
|
||||
|
||||
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true);
|
||||
if (r_error) {
|
||||
memdelete(codegen.generator);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
|
||||
if (field->use_conversion_assign) {
|
||||
codegen.generator->write_assign_with_conversion(temp, src_address);
|
||||
} else {
|
||||
codegen.generator->write_assign(temp, src_address);
|
||||
}
|
||||
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
|
||||
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
}
|
||||
|
||||
if (p_script->has_method(GDScriptLanguage::get_singleton()->strings._static_init)) {
|
||||
codegen.generator->write_newline(p_class->start_line);
|
||||
codegen.generator->write_call(GDScriptCodeGenerator::Address(), class_addr, GDScriptLanguage::get_singleton()->strings._static_init, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (EngineDebugger::is_active()) {
|
||||
String signature;
|
||||
// Path.
|
||||
if (!p_script->get_script_path().is_empty()) {
|
||||
signature += p_script->get_script_path();
|
||||
}
|
||||
// Location.
|
||||
signature += "::0";
|
||||
|
||||
// Function and class.
|
||||
|
||||
if (p_class->identifier) {
|
||||
signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
|
||||
} else {
|
||||
signature += "::" + String(func_name);
|
||||
}
|
||||
|
||||
codegen.generator->set_signature(signature);
|
||||
}
|
||||
#endif
|
||||
|
||||
codegen.generator->set_initial_line(p_class->start_line);
|
||||
|
||||
GDScriptFunction *gd_function = codegen.generator->write_end();
|
||||
|
||||
memdelete(codegen.generator);
|
||||
|
||||
return gd_function;
|
||||
}
|
||||
|
||||
Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
|
||||
Error err = OK;
|
||||
|
||||
|
@ -2236,19 +2397,28 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
|
|||
}
|
||||
member_functions.clear();
|
||||
|
||||
p_script->static_variables.clear();
|
||||
|
||||
if (p_script->implicit_initializer) {
|
||||
memdelete(p_script->implicit_initializer);
|
||||
}
|
||||
if (p_script->implicit_ready) {
|
||||
memdelete(p_script->implicit_ready);
|
||||
}
|
||||
if (p_script->static_initializer) {
|
||||
memdelete(p_script->static_initializer);
|
||||
}
|
||||
|
||||
p_script->member_functions.clear();
|
||||
p_script->member_indices.clear();
|
||||
p_script->member_info.clear();
|
||||
p_script->static_variables_indices.clear();
|
||||
p_script->static_variables.clear();
|
||||
p_script->_signals.clear();
|
||||
p_script->initializer = nullptr;
|
||||
p_script->implicit_initializer = nullptr;
|
||||
p_script->implicit_ready = nullptr;
|
||||
p_script->static_initializer = nullptr;
|
||||
|
||||
p_script->clearing = false;
|
||||
|
||||
|
@ -2357,9 +2527,14 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
|
|||
prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
}
|
||||
|
||||
if (variable->is_static) {
|
||||
minfo.index = p_script->static_variables_indices.size();
|
||||
p_script->static_variables_indices[name] = minfo;
|
||||
} else {
|
||||
p_script->member_info[name] = prop_info;
|
||||
p_script->member_indices[name] = minfo;
|
||||
p_script->members.insert(name);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (variable->initializer != nullptr && variable->initializer->is_constant) {
|
||||
|
@ -2427,6 +2602,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
|
|||
}
|
||||
}
|
||||
|
||||
p_script->static_variables.resize(p_script->static_variables_indices.size());
|
||||
|
||||
parsed_classes.insert(p_script);
|
||||
parsing_classes.erase(p_script);
|
||||
|
||||
|
@ -2503,6 +2680,15 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
|
|||
}
|
||||
}
|
||||
|
||||
if (p_class->has_static_data) {
|
||||
Error err = OK;
|
||||
GDScriptFunction *func = _make_static_initializer(err, p_script, p_class);
|
||||
p_script->static_initializer = func;
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
//validate instances if keeping state
|
||||
|
@ -2552,6 +2738,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
|
|||
}
|
||||
#endif //DEBUG_ENABLED
|
||||
|
||||
has_static_data = p_class->has_static_data;
|
||||
|
||||
for (int i = 0; i < p_class->members.size(); i++) {
|
||||
if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
|
||||
continue;
|
||||
|
@ -2564,6 +2752,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
|
|||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
has_static_data = has_static_data || inner_class->has_static_data;
|
||||
}
|
||||
|
||||
p_script->_init_rpc_methods_properties();
|
||||
|
@ -2650,6 +2840,10 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
|
|||
return err;
|
||||
}
|
||||
|
||||
if (has_static_data && !root->annotated_static_unload) {
|
||||
GDScriptCache::add_static_script(p_script);
|
||||
}
|
||||
|
||||
return GDScriptCache::finish_compiling(main_script->get_path());
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ class GDScriptCompiler {
|
|||
HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
|
||||
HashMap<StringName, GDScriptCodeGenerator::Address> locals;
|
||||
List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
|
||||
bool is_static = false;
|
||||
|
||||
GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) {
|
||||
uint32_t addr = generator->add_local(p_name, p_type);
|
||||
|
@ -130,6 +131,7 @@ class GDScriptCompiler {
|
|||
void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
|
||||
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
|
||||
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
|
||||
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
|
||||
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
|
||||
Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
|
||||
Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
|
||||
|
@ -138,6 +140,7 @@ class GDScriptCompiler {
|
|||
StringName source;
|
||||
String error;
|
||||
GDScriptParser::ExpressionNode *awaited_node = nullptr;
|
||||
bool has_static_data = false;
|
||||
|
||||
public:
|
||||
static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);
|
||||
|
|
|
@ -2992,6 +2992,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
|
|||
|
||||
List<MethodInfo> virtual_methods;
|
||||
ClassDB::get_virtual_methods(class_name, &virtual_methods);
|
||||
|
||||
{
|
||||
// Not truly a virtual method, but can also be "overridden".
|
||||
MethodInfo static_init("_static_init");
|
||||
static_init.return_val.type = Variant::NIL;
|
||||
static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL;
|
||||
virtual_methods.push_back(static_init);
|
||||
}
|
||||
|
||||
for (const MethodInfo &mi : virtual_methods) {
|
||||
String method_hint = mi.name;
|
||||
if (method_hint.contains(":")) {
|
||||
|
|
|
@ -409,7 +409,8 @@ public:
|
|||
ADDR_TYPE_STACK = 0,
|
||||
ADDR_TYPE_CONSTANT = 1,
|
||||
ADDR_TYPE_MEMBER = 2,
|
||||
ADDR_TYPE_MAX = 3,
|
||||
ADDR_TYPE_STATIC_VAR = 3,
|
||||
ADDR_TYPE_MAX = 4,
|
||||
};
|
||||
|
||||
enum FixedAddresses {
|
||||
|
|
|
@ -81,6 +81,8 @@ GDScriptParser::GDScriptParser() {
|
|||
// TODO: Should this be static?
|
||||
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
|
||||
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
|
||||
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
|
||||
|
||||
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
|
||||
// Export annotations.
|
||||
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
|
||||
|
@ -623,7 +625,7 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
GDScriptParser::ClassNode *GDScriptParser::parse_class() {
|
||||
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
|
||||
ClassNode *n_class = alloc_node<ClassNode>();
|
||||
|
||||
ClassNode *previous_class = current_class;
|
||||
|
@ -724,7 +726,7 @@ void GDScriptParser::parse_extends() {
|
|||
}
|
||||
|
||||
template <class T>
|
||||
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
|
||||
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
|
||||
advance();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
@ -749,7 +751,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
|
|||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
T *member = (this->*p_parse_function)();
|
||||
T *member = (this->*p_parse_function)(p_is_static);
|
||||
if (member == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
@ -803,10 +805,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
|
|||
|
||||
void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
||||
bool class_end = false;
|
||||
bool next_is_static = false;
|
||||
while (!class_end && !is_at_end()) {
|
||||
switch (current.type) {
|
||||
GDScriptTokenizer::Token token = current;
|
||||
switch (token.type) {
|
||||
case GDScriptTokenizer::Token::VAR:
|
||||
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
|
||||
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
|
||||
if (next_is_static) {
|
||||
current_class->has_static_data = true;
|
||||
}
|
||||
break;
|
||||
case GDScriptTokenizer::Token::CONST:
|
||||
parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
|
||||
|
@ -814,9 +821,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
|||
case GDScriptTokenizer::Token::SIGNAL:
|
||||
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
|
||||
break;
|
||||
case GDScriptTokenizer::Token::STATIC:
|
||||
case GDScriptTokenizer::Token::FUNC:
|
||||
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
|
||||
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
|
||||
break;
|
||||
case GDScriptTokenizer::Token::CLASS:
|
||||
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
|
||||
|
@ -824,6 +830,13 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
|||
case GDScriptTokenizer::Token::ENUM:
|
||||
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
|
||||
break;
|
||||
case GDScriptTokenizer::Token::STATIC: {
|
||||
advance();
|
||||
next_is_static = true;
|
||||
if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) {
|
||||
push_error(R"(Expected "func" or "var" after "static".)");
|
||||
}
|
||||
} break;
|
||||
case GDScriptTokenizer::Token::ANNOTATION: {
|
||||
advance();
|
||||
|
||||
|
@ -870,6 +883,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
|||
advance();
|
||||
break;
|
||||
}
|
||||
if (token.type != GDScriptTokenizer::Token::STATIC) {
|
||||
next_is_static = false;
|
||||
}
|
||||
if (panic_mode) {
|
||||
synchronize();
|
||||
}
|
||||
|
@ -879,11 +895,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
|||
}
|
||||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
|
||||
return parse_variable(true);
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
|
||||
return parse_variable(p_is_static, true);
|
||||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
|
||||
VariableNode *variable = alloc_node<VariableNode>();
|
||||
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
|
||||
|
@ -893,6 +909,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
|
|||
|
||||
variable->identifier = parse_identifier();
|
||||
variable->export_info.name = variable->identifier->name;
|
||||
variable->is_static = p_is_static;
|
||||
|
||||
if (match(GDScriptTokenizer::Token::COLON)) {
|
||||
if (check(GDScriptTokenizer::Token::NEWLINE)) {
|
||||
|
@ -1036,6 +1053,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
|
|||
complete_extents(identifier);
|
||||
identifier->name = "@" + p_variable->identifier->name + "_setter";
|
||||
function->identifier = identifier;
|
||||
function->is_static = p_variable->is_static;
|
||||
|
||||
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
|
||||
|
||||
|
@ -1087,6 +1105,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
|
|||
complete_extents(identifier);
|
||||
identifier->name = "@" + p_variable->identifier->name + "_getter";
|
||||
function->identifier = identifier;
|
||||
function->is_static = p_variable->is_static;
|
||||
|
||||
FunctionNode *previous_function = current_function;
|
||||
current_function = function;
|
||||
|
@ -1111,7 +1130,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
|
|||
}
|
||||
}
|
||||
|
||||
GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
|
||||
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
|
||||
ConstantNode *constant = alloc_node<ConstantNode>();
|
||||
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
|
||||
|
@ -1178,7 +1197,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
|
|||
return parameter;
|
||||
}
|
||||
|
||||
GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
|
||||
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
|
||||
SignalNode *signal = alloc_node<SignalNode>();
|
||||
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
|
||||
|
@ -1223,7 +1242,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
|
|||
return signal;
|
||||
}
|
||||
|
||||
GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
||||
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
|
||||
EnumNode *enum_node = alloc_node<EnumNode>();
|
||||
bool named = false;
|
||||
|
||||
|
@ -1372,23 +1391,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
|
|||
}
|
||||
}
|
||||
|
||||
if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) {
|
||||
if (!p_function->is_static) {
|
||||
push_error(R"(Static constructor must be declared static.)");
|
||||
}
|
||||
if (p_function->parameters.size() != 0) {
|
||||
push_error(R"(Static constructor cannot have parameters.)");
|
||||
}
|
||||
current_class->has_static_data = true;
|
||||
}
|
||||
|
||||
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
||||
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
|
||||
}
|
||||
|
||||
GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
|
||||
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
|
||||
FunctionNode *function = alloc_node<FunctionNode>();
|
||||
|
||||
bool _static = false;
|
||||
if (previous.type == GDScriptTokenizer::Token::STATIC) {
|
||||
// TODO: Improve message if user uses "static" with "var" or "const"
|
||||
if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
|
||||
complete_extents(function);
|
||||
return nullptr;
|
||||
}
|
||||
_static = true;
|
||||
}
|
||||
|
||||
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
|
||||
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
|
||||
|
@ -1400,7 +1419,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
|
|||
current_function = function;
|
||||
|
||||
function->identifier = parse_identifier();
|
||||
function->is_static = _static;
|
||||
function->is_static = p_is_static;
|
||||
|
||||
SuiteNode *body = alloc_node<SuiteNode>();
|
||||
SuiteNode *previous_suite = current_suite;
|
||||
|
@ -1612,11 +1631,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
|||
break;
|
||||
case GDScriptTokenizer::Token::VAR:
|
||||
advance();
|
||||
result = parse_variable();
|
||||
result = parse_variable(false, false);
|
||||
break;
|
||||
case GDScriptTokenizer::Token::CONST:
|
||||
advance();
|
||||
result = parse_constant();
|
||||
result = parse_constant(false);
|
||||
break;
|
||||
case GDScriptTokenizer::Token::IF:
|
||||
advance();
|
||||
|
@ -1646,7 +1665,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
|||
advance();
|
||||
ReturnNode *n_return = alloc_node<ReturnNode>();
|
||||
if (!is_statement_end()) {
|
||||
if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
|
||||
if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) {
|
||||
push_error(R"(Constructor cannot return a value.)");
|
||||
}
|
||||
n_return->return_value = parse_expression(false);
|
||||
|
@ -4101,6 +4120,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) {
|
||||
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
|
||||
ClassNode *p_class = static_cast<ClassNode *>(p_target);
|
||||
if (p_class->annotated_static_unload) {
|
||||
push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
|
||||
return false;
|
||||
}
|
||||
p_class->annotated_static_unload = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
|
||||
switch (type) {
|
||||
case CONSTANT:
|
||||
|
|
|
@ -709,6 +709,8 @@ public:
|
|||
ClassNode *outer = nullptr;
|
||||
bool extends_used = false;
|
||||
bool onready_used = false;
|
||||
bool has_static_data = false;
|
||||
bool annotated_static_unload = false;
|
||||
String extends_path;
|
||||
Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
|
||||
DataType base_type;
|
||||
|
@ -847,6 +849,7 @@ public:
|
|||
LOCAL_BIND, // Pattern bind.
|
||||
MEMBER_SIGNAL,
|
||||
MEMBER_VARIABLE,
|
||||
STATIC_VARIABLE,
|
||||
MEMBER_CONSTANT,
|
||||
INHERITED_VARIABLE,
|
||||
};
|
||||
|
@ -1202,6 +1205,7 @@ public:
|
|||
bool onready = false;
|
||||
PropertyInfo export_info;
|
||||
int assignments = 0;
|
||||
bool is_static = false;
|
||||
#ifdef TOOLS_ENABLED
|
||||
String doc_description;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
@ -1405,16 +1409,16 @@ private:
|
|||
|
||||
// Main blocks.
|
||||
void parse_program();
|
||||
ClassNode *parse_class();
|
||||
ClassNode *parse_class(bool p_is_static);
|
||||
void parse_class_name();
|
||||
void parse_extends();
|
||||
void parse_class_body(bool p_is_multiline);
|
||||
template <class T>
|
||||
void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
|
||||
SignalNode *parse_signal();
|
||||
EnumNode *parse_enum();
|
||||
void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
|
||||
SignalNode *parse_signal(bool p_is_static);
|
||||
EnumNode *parse_enum(bool p_is_static);
|
||||
ParameterNode *parse_parameter();
|
||||
FunctionNode *parse_function();
|
||||
FunctionNode *parse_function(bool p_is_static);
|
||||
void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
|
||||
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
|
||||
// Annotations
|
||||
|
@ -1431,14 +1435,15 @@ private:
|
|||
bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target);
|
||||
bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
|
||||
bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target);
|
||||
bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target);
|
||||
// Statements.
|
||||
Node *parse_statement();
|
||||
VariableNode *parse_variable();
|
||||
VariableNode *parse_variable(bool p_allow_property);
|
||||
VariableNode *parse_variable(bool p_is_static);
|
||||
VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
|
||||
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
|
||||
void parse_property_getter(VariableNode *p_variable);
|
||||
void parse_property_setter(VariableNode *p_variable);
|
||||
ConstantNode *parse_constant();
|
||||
ConstantNode *parse_constant(bool p_is_static);
|
||||
AssertNode *parse_assert();
|
||||
BreakNode *parse_break();
|
||||
ContinueNode *parse_continue();
|
||||
|
|
|
@ -680,10 +680,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||
bool awaited = false;
|
||||
#endif
|
||||
#ifdef DEBUG_ENABLED
|
||||
int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
|
||||
int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
|
||||
#endif
|
||||
|
||||
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
|
||||
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
OPCODE_WHILE(ip < _code_size) {
|
||||
|
|
|
@ -185,6 +185,9 @@ String GDScriptWarning::get_message() const {
|
|||
case ONREADY_WITH_EXPORT: {
|
||||
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
|
||||
}
|
||||
case REDUNDANT_STATIC_UNLOAD: {
|
||||
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
|
||||
}
|
||||
case WARNING_MAX:
|
||||
break; // Can't happen, but silences warning
|
||||
}
|
||||
|
@ -254,6 +257,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
|
|||
"NATIVE_METHOD_OVERRIDE",
|
||||
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
|
||||
"ONREADY_WITH_EXPORT",
|
||||
"REDUNDANT_STATIC_UNLOAD",
|
||||
};
|
||||
|
||||
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
|
||||
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
|
||||
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
|
||||
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
|
||||
WARNING_MAX,
|
||||
};
|
||||
|
||||
|
@ -130,6 +131,7 @@ public:
|
|||
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
|
||||
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
|
||||
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
|
||||
WARN, // REDUNDANT_STATIC_UNLOAD
|
||||
};
|
||||
|
||||
static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
|
||||
|
|
|
@ -566,6 +566,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
|
|||
ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
|
||||
}
|
||||
|
||||
// Setup output handlers.
|
||||
ErrorHandlerData error_data(&result, this);
|
||||
|
||||
_print_handler.userdata = &result;
|
||||
_error_handler.userdata = &error_data;
|
||||
add_print_handler(&_print_handler);
|
||||
add_error_handler(&_error_handler);
|
||||
|
||||
script->reload();
|
||||
|
||||
// Create object instance for test.
|
||||
|
@ -577,14 +585,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
|
|||
obj->set_script(script);
|
||||
GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
|
||||
|
||||
// Setup output handlers.
|
||||
ErrorHandlerData error_data(&result, this);
|
||||
|
||||
_print_handler.userdata = &result;
|
||||
_error_handler.userdata = &error_data;
|
||||
add_print_handler(&_print_handler);
|
||||
add_error_handler(&_error_handler);
|
||||
|
||||
// Call test function.
|
||||
Callable::CallError call_err;
|
||||
instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
static func _static_init() -> int:
|
||||
print("static init")
|
||||
|
||||
func test():
|
||||
print("done")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Static constructor cannot have an explicit return type.
|
|
@ -0,0 +1,9 @@
|
|||
@static_unload
|
||||
|
||||
func non_static():
|
||||
return "non static"
|
||||
|
||||
static var static_var = non_static()
|
||||
|
||||
func test():
|
||||
print("does not run")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Cannot call non-static function "non_static()" for static variable initializer.
|
|
@ -0,0 +1,5 @@
|
|||
func _static_init():
|
||||
print("static init")
|
||||
|
||||
func test():
|
||||
print("done")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_PARSER_ERROR
|
||||
Static constructor must be declared static.
|
|
@ -0,0 +1,6 @@
|
|||
static func _static_init():
|
||||
print("static init")
|
||||
return true
|
||||
|
||||
func test():
|
||||
print("done")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_PARSER_ERROR
|
||||
Constructor cannot return a value.
|
|
@ -0,0 +1,13 @@
|
|||
@static_unload
|
||||
|
||||
static var foo = "bar"
|
||||
|
||||
static func _static_init():
|
||||
print("static init called")
|
||||
prints("foo is", foo)
|
||||
|
||||
func test():
|
||||
var _lambda = func _static_init():
|
||||
print("lambda does not conflict with static constructor")
|
||||
|
||||
print("done")
|
|
@ -0,0 +1,4 @@
|
|||
GDTEST_OK
|
||||
static init called
|
||||
foo is bar
|
||||
done
|
|
@ -0,0 +1,56 @@
|
|||
@static_unload
|
||||
|
||||
static var perm := 0
|
||||
|
||||
static var prop := "Hello!":
|
||||
get: return prop + " suffix"
|
||||
set(value): prop = "prefix " + str(value)
|
||||
|
||||
static func get_data():
|
||||
return "data"
|
||||
|
||||
static var data = get_data()
|
||||
|
||||
class Inner:
|
||||
static var prop := "inner"
|
||||
static func _static_init() -> void:
|
||||
prints("Inner._static_init", prop)
|
||||
|
||||
class InnerInner:
|
||||
static var prop := "inner inner"
|
||||
static func _static_init() -> void:
|
||||
prints("InnerInner._static_init", prop)
|
||||
|
||||
func test():
|
||||
prints("data:", data)
|
||||
|
||||
prints("perm:", perm)
|
||||
prints("prop:", prop)
|
||||
|
||||
perm = 1
|
||||
prop = "World!"
|
||||
|
||||
prints("perm:", perm)
|
||||
prints("prop:", prop)
|
||||
|
||||
print("other.perm:", StaticVariablesOther.perm)
|
||||
print("other.prop:", StaticVariablesOther.prop)
|
||||
|
||||
StaticVariablesOther.perm = 2
|
||||
StaticVariablesOther.prop = "foo"
|
||||
|
||||
print("other.perm:", StaticVariablesOther.perm)
|
||||
print("other.prop:", StaticVariablesOther.prop)
|
||||
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var path = get_script().get_path().get_base_dir()
|
||||
var other = load(path + "/static_variables_load.gd")
|
||||
|
||||
print("load.perm:", other.perm)
|
||||
print("load.prop:", other.prop)
|
||||
|
||||
other.perm = 3
|
||||
other.prop = "bar"
|
||||
|
||||
print("load.perm:", other.perm)
|
||||
print("load.prop:", other.prop)
|
|
@ -0,0 +1,16 @@
|
|||
GDTEST_OK
|
||||
Inner._static_init inner
|
||||
InnerInner._static_init inner inner
|
||||
data: data
|
||||
perm: 0
|
||||
prop: prefix Hello! suffix
|
||||
perm: 1
|
||||
prop: prefix World! suffix
|
||||
other.perm:0
|
||||
other.prop:prefix Hello! suffix
|
||||
other.perm:2
|
||||
other.prop:prefix foo suffix
|
||||
load.perm:0
|
||||
load.prop:prefix Hello! suffix
|
||||
load.perm:3
|
||||
load.prop:prefix bar suffix
|
|
@ -0,0 +1,10 @@
|
|||
@static_unload
|
||||
|
||||
static var perm := 0
|
||||
|
||||
static var prop := "Hello!":
|
||||
get: return prop + " suffix"
|
||||
set(value): prop = "prefix " + str(value)
|
||||
|
||||
func test():
|
||||
print("ok")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_OK
|
||||
ok
|
|
@ -0,0 +1,11 @@
|
|||
@static_unload
|
||||
class_name StaticVariablesOther
|
||||
|
||||
static var perm := 0
|
||||
|
||||
static var prop := "Hello!":
|
||||
get: return prop + " suffix"
|
||||
set(value): prop = "prefix " + str(value)
|
||||
|
||||
func test():
|
||||
print("ok")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_OK
|
||||
ok
|
Loading…
Reference in New Issue