Deprecate NOTIFICATION_MOVED_IN_PARENT

* NOTIFICATION_MOVED_IN_PARENT makes node children management very inefficient.
* Replaced by a NOTIFICATION_CHILD_ORDER_CHANGED (and children_changed signal).
* Most of the previous tasks carried out by NOTIFICATION_MOVED_IN_PARENT are now done not more than a single time per frame.

This PR breaks compatibility (although this notification was very rarely used, even within the engine), but provides an alternate way to do the same.
This commit is contained in:
lawnjelly 2024-04-19 09:53:06 +01:00
parent 1869243644
commit d56d1ff4d2
14 changed files with 224 additions and 47 deletions

View file

@ -797,6 +797,11 @@
When this signal is received, the child [code]node[/code] is still in the tree and valid. This signal is emitted [i]after[/i] the child node's own [signal tree_exiting] and [constant NOTIFICATION_EXIT_TREE]. When this signal is received, the child [code]node[/code] is still in the tree and valid. This signal is emitted [i]after[/i] the child node's own [signal tree_exiting] and [constant NOTIFICATION_EXIT_TREE].
</description> </description>
</signal> </signal>
<signal name="child_order_changed">
<description>
Emitted when the list of children is changed. This happens when child nodes are added, moved, or removed.
</description>
</signal>
<signal name="ready"> <signal name="ready">
<description> <description>
Emitted when the node is ready. Emitted when the node is ready.
@ -835,7 +840,7 @@
This notification is emitted [i]after[/i] the related [signal tree_exiting]. This notification is emitted [i]after[/i] the related [signal tree_exiting].
</constant> </constant>
<constant name="NOTIFICATION_MOVED_IN_PARENT" value="12"> <constant name="NOTIFICATION_MOVED_IN_PARENT" value="12">
Notification received when the node is moved in the parent. [i]Deprecated.[/i] This notification is no longer emitted. Use [constant NOTIFICATION_CHILD_ORDER_CHANGED] instead.
</constant> </constant>
<constant name="NOTIFICATION_READY" value="13"> <constant name="NOTIFICATION_READY" value="13">
Notification received when the node is ready. See [method _ready]. Notification received when the node is ready. See [method _ready].
@ -874,6 +879,9 @@
<constant name="NOTIFICATION_PATH_CHANGED" value="23"> <constant name="NOTIFICATION_PATH_CHANGED" value="23">
Notification received when the node's [NodePath] changed. Notification received when the node's [NodePath] changed.
</constant> </constant>
<constant name="NOTIFICATION_CHILD_ORDER_CHANGED" value="24">
Notification received when the list of children is changed. This happens when child nodes are added, moved, or removed.
</constant>
<constant name="NOTIFICATION_INTERNAL_PROCESS" value="25"> <constant name="NOTIFICATION_INTERNAL_PROCESS" value="25">
Notification received every frame when the internal process flag is set (see [method set_process_internal]). Notification received every frame when the internal process flag is set (see [method set_process_internal]).
</constant> </constant>

View file

@ -2399,6 +2399,12 @@ bool Main::iteration() {
exit = true; exit = true;
} }
visual_server_callbacks->flush(); visual_server_callbacks->flush();
// Ensure that VisualServer is kept up to date at least once with any ordering changes
// of canvas items before a render.
// This ensures this will be done at least once in apps that create their own MainLoop.
Viewport::flush_canvas_parents_dirty_order();
message_queue->flush(); message_queue->flush();
VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames. VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames.

View file

@ -611,20 +611,6 @@ void CanvasItem::_notification(int p_what) {
notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
} }
} break; } break;
case NOTIFICATION_MOVED_IN_PARENT: {
if (!is_inside_tree()) {
break;
}
if (canvas_group != "") {
get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, canvas_group, "_toplevel_raise_self");
} else {
CanvasItem *p = get_parent_item();
ERR_FAIL_COND(!p);
VisualServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
}
} break;
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
if (xform_change.in_list()) { if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change); get_tree()->xform_change_list.remove(&xform_change);
@ -662,6 +648,20 @@ void CanvasItem::_name_changed_notify() {
} }
#endif #endif
void CanvasItem::update_draw_order() {
if (!is_inside_tree()) {
return;
}
if (canvas_group != "") {
get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, canvas_group, "_toplevel_raise_self");
} else {
CanvasItem *p = get_parent_item();
ERR_FAIL_NULL(p);
VisualServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
}
}
void CanvasItem::update() { void CanvasItem::update() {
if (!is_inside_tree()) { if (!is_inside_tree()) {
return; return;

View file

@ -296,6 +296,8 @@ public:
virtual Transform2D _edit_get_transform() const; virtual Transform2D _edit_get_transform() const;
#endif #endif
void update_draw_order();
/* VISIBILITY */ /* VISIBILITY */
void set_visible(bool p_visible); void set_visible(bool p_visible);

View file

@ -30,9 +30,20 @@
#include "skeleton_2d.h" #include "skeleton_2d.h"
void Bone2D::_order_changed_in_parent() {
if (skeleton) {
skeleton->_make_bone_setup_dirty();
}
}
void Bone2D::_notification(int p_what) { void Bone2D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) { if (p_what == NOTIFICATION_ENTER_TREE) {
Node *parent = get_parent(); Node *parent = get_parent();
if (parent) {
parent->connect("child_order_changed", this, "_order_changed_in_parent");
}
parent_bone = Object::cast_to<Bone2D>(parent); parent_bone = Object::cast_to<Bone2D>(parent);
skeleton = nullptr; skeleton = nullptr;
while (parent) { while (parent) {
@ -59,13 +70,13 @@ void Bone2D::_notification(int p_what) {
skeleton->_make_transform_dirty(); skeleton->_make_transform_dirty();
} }
} }
if (p_what == NOTIFICATION_MOVED_IN_PARENT) {
if (skeleton) {
skeleton->_make_bone_setup_dirty();
}
}
if (p_what == NOTIFICATION_EXIT_TREE) { if (p_what == NOTIFICATION_EXIT_TREE) {
Node *parent = get_parent();
if (parent) {
parent->disconnect("child_order_changed", this, "_order_changed_in_parent");
}
if (skeleton) { if (skeleton) {
for (int i = 0; i < skeleton->bones.size(); i++) { for (int i = 0; i < skeleton->bones.size(); i++) {
if (skeleton->bones[i].bone == this) { if (skeleton->bones[i].bone == this) {
@ -89,6 +100,8 @@ void Bone2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length);
ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length);
ClassDB::bind_method(D_METHOD("_order_changed_in_parent"), &Bone2D::_order_changed_in_parent);
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length");
} }

View file

@ -53,6 +53,7 @@ class Bone2D : public Node2D {
protected: protected:
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
void _order_changed_in_parent();
public: public:
void set_rest(const Transform2D &p_rest); void set_rest(const Transform2D &p_rest);

View file

@ -592,22 +592,6 @@ void Control::_notification(int p_notification) {
} }
*/ */
} break;
case NOTIFICATION_MOVED_IN_PARENT: {
// some parents need to know the order of the children to draw (like TabContainer)
// update if necessary
if (data.parent) {
data.parent->update();
}
update();
if (data.SI) {
get_viewport()->_gui_set_subwindow_order_dirty();
}
if (data.RI) {
get_viewport()->_gui_set_root_order_dirty();
}
} break; } break;
case NOTIFICATION_RESIZED: { case NOTIFICATION_RESIZED: {
emit_signal(SceneStringNames::get_singleton()->resized); emit_signal(SceneStringNames::get_singleton()->resized);
@ -2665,6 +2649,15 @@ Control::GrowDirection Control::get_v_grow_direction() const {
return data.v_grow; return data.v_grow;
} }
void Control::_query_order_update(bool &r_subwindow_order_dirty, bool &r_root_order_dirty) const {
if (data.SI) {
r_subwindow_order_dirty = true;
}
if (data.RI) {
r_root_order_dirty = true;
}
}
void Control::_bind_methods() { void Control::_bind_methods() {
//ClassDB::bind_method(D_METHOD("_window_resize_event"),&Control::_window_resize_event); //ClassDB::bind_method(D_METHOD("_window_resize_event"),&Control::_window_resize_event);
ClassDB::bind_method(D_METHOD("_size_changed"), &Control::_size_changed); ClassDB::bind_method(D_METHOD("_size_changed"), &Control::_size_changed);

View file

@ -508,6 +508,8 @@ public:
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
virtual String get_configuration_warning() const; virtual String get_configuration_warning() const;
void _query_order_update(bool &r_subwindow_order_dirty, bool &r_root_order_dirty) const;
Control(); Control();
~Control(); ~Control();
}; };

View file

@ -228,15 +228,16 @@ void CanvasLayer::_notification(int p_what) {
viewport = RID(); viewport = RID();
_update_follow_viewport(false); _update_follow_viewport(false);
} break; } break;
case NOTIFICATION_MOVED_IN_PARENT: {
// Note: As this step requires traversing the entire scene tree, it is thus expensive
// to move the canvas layer multiple times. Take special care when deleting / moving
// multiple nodes to prevent multiple NOTIFICATION_MOVED_IN_PARENT occurring.
_update_layer_orders();
} break;
} }
} }
void CanvasLayer::update_draw_order() {
// Note: As this step requires traversing the entire scene tree, it is thus expensive
// to move the canvas layer multiple times. Take special care when deleting / moving
// multiple nodes to prevent this happening multiple times.
_update_layer_orders();
}
Size2 CanvasLayer::get_viewport_size() const { Size2 CanvasLayer::get_viewport_size() const {
if (!is_inside_tree()) { if (!is_inside_tree()) {
return Size2(1, 1); return Size2(1, 1);

View file

@ -70,6 +70,8 @@ protected:
static void _bind_methods(); static void _bind_methods();
public: public:
void update_draw_order();
void set_layer(int p_xform); void set_layer(int p_xform);
int get_layer() const; int get_layer() const;

View file

@ -426,9 +426,8 @@ void Node::move_child(Node *p_child, int p_pos) {
} }
// notification second // notification second
move_child_notify(p_child); move_child_notify(p_child);
for (int i = motion_from; i <= motion_to; i++) { Viewport::notify_canvas_parent_children_moved(*this, motion_from, motion_to + 1);
data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT);
}
p_child->_propagate_groups_dirty(); p_child->_propagate_groups_dirty();
data.blocked--; data.blocked--;
@ -1364,9 +1363,11 @@ void Node::remove_child(Node *p_child) {
for (int i = idx; i < child_count; i++) { for (int i = idx; i < child_count; i++) {
children[i]->data.pos = i; children[i]->data.pos = i;
children[i]->notification(NOTIFICATION_MOVED_IN_PARENT);
} }
Viewport::notify_canvas_parent_children_moved(*this, idx, child_count);
Viewport::notify_canvas_parent_child_count_reduced(*this);
p_child->data.parent = nullptr; p_child->data.parent = nullptr;
p_child->data.pos = -1; p_child->data.pos = -1;
@ -3193,6 +3194,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_DRAG_BEGIN); BIND_CONSTANT(NOTIFICATION_DRAG_BEGIN);
BIND_CONSTANT(NOTIFICATION_DRAG_END); BIND_CONSTANT(NOTIFICATION_DRAG_END);
BIND_CONSTANT(NOTIFICATION_PATH_CHANGED); BIND_CONSTANT(NOTIFICATION_PATH_CHANGED);
BIND_CONSTANT(NOTIFICATION_CHILD_ORDER_CHANGED);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
@ -3233,6 +3235,7 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("tree_exited")); ADD_SIGNAL(MethodInfo("tree_exited"));
ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_SIGNAL(MethodInfo("child_exiting_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_SIGNAL(MethodInfo("child_exiting_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_SIGNAL(MethodInfo("child_order_changed"));
ADD_PROPERTY(PropertyInfo(Variant::INT, "pause_mode", PROPERTY_HINT_ENUM, "Inherit,Stop,Process"), "set_pause_mode", "get_pause_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "pause_mode", PROPERTY_HINT_ENUM, "Inherit,Stop,Process"), "set_pause_mode", "get_pause_mode");

View file

@ -134,6 +134,11 @@ private:
int process_priority; int process_priority;
// If canvas item children of a node change child order,
// we store this information in the scenetree in a temporary structure
// allocated on demand per node.
uint32_t canvas_parent_id = UINT32_MAX;
// Keep bitpacked values together to get better packing // Keep bitpacked values together to get better packing
PauseMode pause_mode : 2; PauseMode pause_mode : 2;
PhysicsInterpolationMode physics_interpolation_mode : 2; PhysicsInterpolationMode physics_interpolation_mode : 2;
@ -279,7 +284,7 @@ public:
NOTIFICATION_DRAG_BEGIN = 21, NOTIFICATION_DRAG_BEGIN = 21,
NOTIFICATION_DRAG_END = 22, NOTIFICATION_DRAG_END = 22,
NOTIFICATION_PATH_CHANGED = 23, NOTIFICATION_PATH_CHANGED = 23,
//NOTIFICATION_TRANSLATION_CHANGED = 24, moved below NOTIFICATION_CHILD_ORDER_CHANGED = 24,
NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PROCESS = 25,
NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
NOTIFICATION_POST_ENTER_TREE = 27, NOTIFICATION_POST_ENTER_TREE = 27,
@ -455,6 +460,9 @@ public:
_FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); } _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); }
void reset_physics_interpolation(); void reset_physics_interpolation();
uint32_t get_canvas_parent_id() const { return data.canvas_parent_id; }
void set_canvas_parent_id(uint32_t p_id) { data.canvas_parent_id = p_id; }
bool is_node_ready() const; bool is_node_ready() const;
void request_ready(); void request_ready();

View file

@ -54,6 +54,8 @@
#include "scene/scene_string_names.h" #include "scene/scene_string_names.h"
#include "servers/physics_2d_server.h" #include "servers/physics_2d_server.h"
LocalVector<Viewport::GUI::CanvasParent> Viewport::GUI::canvas_parents_dirty_order;
void ViewportTexture::setup_local_to_scene() { void ViewportTexture::setup_local_to_scene() {
Node *local_scene = get_local_scene(); Node *local_scene = get_local_scene();
if (!local_scene) { if (!local_scene) {
@ -3214,6 +3216,130 @@ bool Viewport::is_handling_input_locally() const {
return handle_input_locally; return handle_input_locally;
} }
void Viewport::notify_canvas_parent_child_count_reduced(const Node &p_parent) {
uint32_t id = p_parent.get_canvas_parent_id();
ERR_FAIL_COND(id == UINT32_MAX);
ERR_FAIL_UNSIGNED_INDEX(id, GUI::canvas_parents_dirty_order.size());
GUI::CanvasParent &d = GUI::canvas_parents_dirty_order[id];
// No pending children moved.
if (!d.last_child_moved_plus_one) {
return;
}
uint32_t num_children = p_parent.get_child_count();
d.last_child_moved_plus_one = MIN(d.last_child_moved_plus_one, num_children);
}
void Viewport::notify_canvas_parent_children_moved(Node &r_parent, uint32_t p_first_child, uint32_t p_last_child_plus_one) {
// First ensure the parent has a CanvasParent allocated.
uint32_t id = r_parent.get_canvas_parent_id();
if (id == UINT32_MAX) {
id = GUI::canvas_parents_dirty_order.size();
r_parent.set_canvas_parent_id(id);
GUI::canvas_parents_dirty_order.resize(id + 1);
GUI::CanvasParent &d = GUI::canvas_parents_dirty_order[id];
d.id = r_parent.get_instance_id();
d.first_child_moved = p_first_child;
d.last_child_moved_plus_one = p_last_child_plus_one;
return;
}
GUI::CanvasParent &d = GUI::canvas_parents_dirty_order[id];
DEV_CHECK_ONCE(d.id == r_parent.get_instance_id());
d.first_child_moved = MIN(d.first_child_moved, p_first_child);
d.last_child_moved_plus_one = MAX(d.last_child_moved_plus_one, p_last_child_plus_one);
}
void Viewport::flush_canvas_parents_dirty_order() {
bool canvas_layers_moved = false;
for (uint32_t n = 0; n < GUI::canvas_parents_dirty_order.size(); n++) {
GUI::CanvasParent &d = GUI::canvas_parents_dirty_order[n];
Object *obj = ObjectDB::get_instance(d.id);
if (!obj) {
// May have been deleted.
continue;
}
Node *node = static_cast<Node *>(obj);
Control *parent_control = Object::cast_to<Control>(node);
// Allow anything subscribing to this signal (skeletons etc)
// to make any changes necessary.
node->emit_signal("child_order_changed");
// Reset the id stored in the node, as this data is cleared after every flush.
node->set_canvas_parent_id(UINT32_MAX);
// This should be very rare, as the last_child_moved_plus_one is usually kept up
// to date when within the scene tree. But it may become out of date outside the scene tree.
// This will cause no problems, just a very small possibility of more notifications being sent than
// necessary in that very rare situation.
if (d.last_child_moved_plus_one > (uint32_t)node->get_child_count()) {
d.last_child_moved_plus_one = node->get_child_count();
}
bool subwindow_order_dirty = false;
bool root_order_dirty = false;
bool control_found = false;
for (uint32_t c = d.first_child_moved; c < d.last_child_moved_plus_one; c++) {
Node *child = node->get_child(c);
CanvasItem *ci = Object::cast_to<CanvasItem>(child);
if (ci) {
ci->update_draw_order();
Control *control = Object::cast_to<Control>(ci);
if (control) {
control->_query_order_update(subwindow_order_dirty, root_order_dirty);
if (parent_control && !control_found) {
control_found = true;
// In case of regressions, this could be moved to AFTER
// the children have been updated.
parent_control->update();
}
control->update();
}
continue;
}
CanvasLayer *cl = Object::cast_to<CanvasLayer>(child);
if (cl) {
// If any canvas layers moved, we need to do an expensive update,
// so we ensure doing this only once.
if (!canvas_layers_moved) {
canvas_layers_moved = true;
cl->update_draw_order();
}
}
}
Viewport *viewport = node->get_viewport();
if (viewport) {
if (subwindow_order_dirty) {
viewport->_gui_set_subwindow_order_dirty();
}
if (root_order_dirty) {
viewport->_gui_set_root_order_dirty();
}
}
node->notification(Node::NOTIFICATION_CHILD_ORDER_CHANGED);
}
GUI::canvas_parents_dirty_order.clear();
}
void Viewport::_validate_property(PropertyInfo &property) const { void Viewport::_validate_property(PropertyInfo &property) const {
if (VisualServer::get_singleton()->is_low_end() && (property.name == "hdr" || property.name == "use_32_bpc_depth" || property.name == "debanding" || property.name == "sharpen_intensity" || property.name == "debug_draw")) { if (VisualServer::get_singleton()->is_low_end() && (property.name == "hdr" || property.name == "use_32_bpc_depth" || property.name == "debanding" || property.name == "sharpen_intensity" || property.name == "debug_draw")) {
// Only available in GLES3. // Only available in GLES3.

View file

@ -326,6 +326,14 @@ private:
bool dragging; bool dragging;
bool drag_successful; bool drag_successful;
struct CanvasParent {
ObjectID id;
uint32_t first_child_moved = UINT32_MAX;
uint32_t last_child_moved_plus_one = 0;
};
static LocalVector<CanvasParent> canvas_parents_dirty_order;
GUI(); GUI();
} gui; } gui;
@ -588,6 +596,10 @@ public:
bool gui_is_dragging() const; bool gui_is_dragging() const;
bool gui_is_drag_successful() const; bool gui_is_drag_successful() const;
static void notify_canvas_parent_children_moved(Node &r_parent, uint32_t p_first_child, uint32_t p_last_child_plus_one);
static void notify_canvas_parent_child_count_reduced(const Node &p_parent);
static void flush_canvas_parents_dirty_order();
Viewport(); Viewport();
~Viewport(); ~Viewport();
}; };