Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Node._scene_instantiated() method and @oninstantiated annotation #87594

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions doc/classes/Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,20 @@
<return type="void" />
<description>
Called when the node is "ready", i.e. when both the node and its children have entered the scene tree. If the node has children, their [method _ready] callbacks get triggered first, and the parent node will receive the ready notification afterwards.
Corresponds to the [constant NOTIFICATION_READY] notification in [method Object._notification]. See also the [code]@onready[/code] annotation for variables.
Usually used for initialization. For even earlier initialization, [method Object._init] may be used. See also [method _enter_tree].
Corresponds to the [constant NOTIFICATION_READY] notification in [method Object._notification]. See also the [annotation @GDScript.@onready] annotation.
Usually used for initialization. For even earlier initialization, [method Object._init] may be used. See also [method _scene_instantiated] and [method _enter_tree].
[b]Note:[/b] This method may be called only once for each node. After removing a node from the scene tree and adding it again, [method _ready] will [b]not[/b] be called a second time. This can be bypassed by requesting another call with [method request_ready], which may be called anywhere before adding the node again.
</description>
</method>
<method name="_scene_instantiated" qualifiers="virtual">
<return type="void" />
<description>
Called on the scene root node when the scene instantiation is complete, after all exported variables have been applied and all child nodes have been added.
Corresponds to the [constant NOTIFICATION_SCENE_INSTANTIATED] notification in [method Object._notification]. See also the [annotation @GDScript.@oninstantiated] annotation.
Can be used for early initialization if you need to access child nodes before the scene enters the tree.
[b]Note:[/b] This method is called only on the scene root node and only when the scene is instantiated via [method PackedScene.instantiate]. This method will [b]not[/b] be called if you create a node using [code]Node.new()[/code].
</description>
</method>
<method name="_shortcut_input" qualifiers="virtual">
<return type="void" />
<param index="0" name="event" type="InputEvent" />
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,9 @@
<member name="debug/gdscript/warnings/native_method_override" type="int" setter="" getter="" default="2">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method in the script overrides a native method, because it may not behave as expected.
</member>
<member name="debug/gdscript/warnings/oninstantiated_with_export" type="int" setter="" getter="" default="2">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@oninstantiated[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected.
</member>
<member name="debug/gdscript/warnings/onready_with_export" type="int" setter="" getter="" default="2">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@onready[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected.
</member>
Expand Down
10 changes: 10 additions & 0 deletions modules/gdscript/doc_classes/@GDScript.xml
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,16 @@
[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@oninstantiated">
<return type="void" />
<description>
Mark the following property as assigned when the [Node] receives notification [constant Node.NOTIFICATION_SCENE_INSTANTIATED]. Values for these properties are not assigned immediately when the node is initialized ([method Object._init]), and instead are computed and stored right before [method Node._scene_instantiated].
[codeblock]
@oninstantiated var character_name: Label = $Label
[/codeblock]
[b]Note:[/b] This annotation should only be used with scene root node variables, since child nodes do not receive the [constant Node.NOTIFICATION_SCENE_INSTANTIATED] notification. See [method Node._scene_instantiated] for details.
</description>
</annotation>
<annotation name="@onready">
<return type="void" />
<description>
Expand Down
25 changes: 25 additions & 0 deletions modules/gdscript/gdscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,15 @@ void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScri
}
}

if (implicit_scene_instantiated) {
for (const Variant &V : implicit_scene_instantiated->constants) {
GDScript *scr = _get_gdscript_from_variant(V);
if (scr != nullptr && scr != p_except) {
scr->_get_dependencies(p_dependencies, p_except);
}
}
}

if (implicit_ready) {
for (const Variant &V : implicit_ready->constants) {
GDScript *scr = _get_gdscript_from_variant(V);
Expand Down Expand Up @@ -1473,6 +1482,11 @@ void GDScript::clear(ClearData *p_clear_data) {
implicit_initializer = nullptr;
}

if (implicit_scene_instantiated) {
clear_data->functions.insert(implicit_scene_instantiated);
implicit_scene_instantiated = nullptr;
}

if (implicit_ready) {
clear_data->functions.insert(implicit_ready);
implicit_ready = nullptr;
Expand Down Expand Up @@ -1896,6 +1910,17 @@ Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_ar
sptr = sptr->_base;
}

// Reset this back for the regular call.
sptr = script.ptr();
} else if (unlikely(p_method == SNAME("_scene_instantiated"))) {
// Call implicit scene_instantiate first, including for the super classes.
while (sptr) {
if (sptr->implicit_scene_instantiated) {
sptr->implicit_scene_instantiated->call(this, nullptr, 0, r_error);
}
sptr = sptr->_base;
}

// Reset this back for the regular call.
sptr = script.ptr();
}
Expand Down
5 changes: 3 additions & 2 deletions modules/gdscript/gdscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,11 @@ class GDScript : public Script {
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif

GDScriptFunction *static_initializer = nullptr;
GDScriptFunction *initializer = nullptr; // Direct pointer to new, faster to locate.
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
GDScriptFunction *implicit_scene_instantiated = nullptr;
GDScriptFunction *implicit_ready = nullptr;
GDScriptFunction *static_initializer = nullptr;

Error _static_init();

Expand Down
15 changes: 12 additions & 3 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -949,13 +949,22 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
static_context = previous_static_context;

#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
if (member.variable->exported) {
switch (member.variable->init_stage) {
case GDScriptParser::VariableNode::INIT_STAGE_NORMAL:
break; // OK.
case GDScriptParser::VariableNode::INIT_STAGE_ONINSTANTIATED:
parser->push_warning(member.variable, GDScriptWarning::ONINSTANTIATED_WITH_EXPORT);
break;
case GDScriptParser::VariableNode::INIT_STAGE_ONREADY:
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
break;
}
}
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->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)) {
if (!member.variable->is_static && member.variable->init_stage == GDScriptParser::VariableNode::INIT_STAGE_NORMAL && 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;
Expand Down
Loading
Loading