Optimize Node children management

* Adding and removing child nodes is now constant time, speed up should be huge.
* Searching for node paths as in ("path/to/node") should be far faster too.

This changes the children management and makes it a hashmap, optimizing most StringName based operations.
Most operations should be severe speed up without breaking compatibility.

This should fix many issues regarding to node access performance, and may also speed up editor start/end, but benchmarks are needed. So if you want to test, please make some benchmarks!

Further performance improvements will be done in the future by removing NOTIFICATION_MOVED_IN_PARENT and replacing by something less laborious.
This commit is contained in:
Juan Linietsky 2023-04-03 22:31:47 +02:00
parent c151d3231f
commit ab5fc22f67
2 changed files with 242 additions and 218 deletions

View File

@ -166,7 +166,7 @@ void Node::_notification(int p_notification) {
// kill children as cleanly as possible
while (data.children.size()) {
Node *child = data.children[data.children.size() - 1]; //begin from the end because its faster and more consistent with creation
Node *child = data.children.last()->value; // begin from the end because its faster and more consistent with creation
memdelete(child);
}
} break;
@ -176,9 +176,10 @@ void Node::_notification(int p_notification) {
void Node::_propagate_ready() {
data.ready_notified = true;
data.blocked++;
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_propagate_ready();
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_ready();
}
data.blocked--;
notification(NOTIFICATION_POST_ENTER_TREE);
@ -228,9 +229,9 @@ void Node::_propagate_enter_tree() {
data.blocked++;
//block while adding children
for (int i = 0; i < data.children.size(); i++) {
if (!data.children[i]->is_inside_tree()) { // could have been added in enter_tree
data.children[i]->_propagate_enter_tree();
for (KeyValue<StringName, Node *> &K : data.children) {
if (!K.value->is_inside_tree()) { // could have been added in enter_tree
K.value->_propagate_enter_tree();
}
}
@ -267,9 +268,11 @@ void Node::_propagate_after_exit_tree() {
}
data.blocked++;
for (int i = data.children.size() - 1; i >= 0; i--) {
data.children[i]->_propagate_after_exit_tree();
for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
I->value->_propagate_after_exit_tree();
}
data.blocked--;
emit_signal(SceneStringNames::get_singleton()->tree_exited);
@ -286,8 +289,8 @@ void Node::_propagate_exit_tree() {
#endif
data.blocked++;
for (int i = data.children.size() - 1; i >= 0; i--) {
data.children[i]->_propagate_exit_tree();
for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
I->value->_propagate_exit_tree();
}
data.blocked--;
@ -329,25 +332,26 @@ void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node.");
_update_children_cache();
// We need to check whether node is internal and move it only in the relevant node range.
if (p_child->_is_internal_front()) {
if (p_child->data.internal_mode == INTERNAL_MODE_FRONT) {
if (p_index < 0) {
p_index += data.internal_children_front;
p_index += data.internal_children_front_count_cache;
}
ERR_FAIL_INDEX_MSG(p_index, data.internal_children_front, vformat("Invalid new child index: %d. Child is internal.", p_index));
ERR_FAIL_INDEX_MSG(p_index, data.internal_children_front_count_cache, vformat("Invalid new child index: %d. Child is internal.", p_index));
_move_child(p_child, p_index);
} else if (p_child->_is_internal_back()) {
} else if (p_child->data.internal_mode == INTERNAL_MODE_BACK) {
if (p_index < 0) {
p_index += data.internal_children_back;
p_index += data.internal_children_back_count_cache;
}
ERR_FAIL_INDEX_MSG(p_index, data.internal_children_back, vformat("Invalid new child index: %d. Child is internal.", p_index));
_move_child(p_child, data.children.size() - data.internal_children_back + p_index);
ERR_FAIL_INDEX_MSG(p_index, data.internal_children_back_count_cache, vformat("Invalid new child index: %d. Child is internal.", p_index));
_move_child(p_child, (int)data.children_cache.size() - data.internal_children_back_count_cache + p_index);
} else {
if (p_index < 0) {
p_index += get_child_count(false);
}
ERR_FAIL_INDEX_MSG(p_index, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child index: %d.", p_index));
_move_child(p_child, p_index + data.internal_children_front);
ERR_FAIL_INDEX_MSG(p_index, (int)data.children_cache.size() + 1 - data.internal_children_front_count_cache - data.internal_children_back_count_cache, vformat("Invalid new child index: %d.", p_index));
_move_child(p_child, p_index + data.internal_children_front_count_cache);
}
}
@ -357,30 +361,32 @@ void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) {
// Specifying one place beyond the end
// means the same as moving to the last index
if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly.
if (p_child->_is_internal_front()) {
if (p_index == data.internal_children_front) {
if (p_child->data.internal_mode == INTERNAL_MODE_FRONT) {
if (p_index == data.internal_children_front_count_cache) {
p_index--;
}
} else if (p_child->_is_internal_back()) {
if (p_index == data.children.size()) {
} else if (p_child->data.internal_mode == INTERNAL_MODE_BACK) {
if (p_index == (int)data.children_cache.size()) {
p_index--;
}
} else {
if (p_index == data.children.size() - data.internal_children_back) {
if (p_index == (int)data.children_cache.size() - data.internal_children_back_count_cache) {
p_index--;
}
}
}
if (p_child->data.index == p_index) {
int child_index = p_child->get_index();
if (child_index == p_index) {
return; //do nothing
}
int motion_from = MIN(p_index, p_child->data.index);
int motion_to = MAX(p_index, p_child->data.index);
int motion_from = MIN(p_index, child_index);
int motion_to = MAX(p_index, child_index);
data.children.remove_at(p_child->data.index);
data.children.insert(p_index, p_child);
data.children_cache.remove_at(child_index);
data.children_cache.insert(p_index, p_child);
if (data.tree) {
data.tree->tree_changed();
@ -389,13 +395,18 @@ void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) {
data.blocked++;
//new pos first
for (int i = motion_from; i <= motion_to; i++) {
data.children[i]->data.index = i;
if (data.children_cache[i]->data.internal_mode == INTERNAL_MODE_DISABLED) {
data.children_cache[i]->data.index = i - data.internal_children_front_count_cache;
} else if (data.children_cache[i]->data.internal_mode == INTERNAL_MODE_BACK) {
data.children_cache[i]->data.index = i - data.internal_children_front_count_cache - data.external_children_count_cache;
} else {
data.children_cache[i]->data.index = i;
}
}
// notification second
move_child_notify(p_child);
notification(NOTIFICATION_CHILD_ORDER_CHANGED);
emit_signal(SNAME("child_order_changed"));
p_child->_propagate_groups_dirty();
data.blocked--;
@ -408,8 +419,8 @@ void Node::_propagate_groups_dirty() {
}
}
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_propagate_groups_dirty();
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_groups_dirty();
}
}
@ -529,8 +540,8 @@ void Node::_propagate_pause_notification(bool p_enable) {
notification(NOTIFICATION_UNPAUSED);
}
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_propagate_pause_notification(p_enable);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_pause_notification(p_enable);
}
}
@ -549,8 +560,8 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int
notification(p_enabled_notification);
}
for (int i = 0; i < data.children.size(); i++) {
Node *c = data.children[i];
for (KeyValue<StringName, Node *> &K : data.children) {
Node *c = K.value;
if (c->data.process_mode == PROCESS_MODE_INHERIT) {
c->_propagate_process_owner(p_owner, p_pause_notification, p_enabled_notification);
}
@ -561,8 +572,8 @@ void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
data.multiplayer_authority = p_peer_id;
if (p_recursive) {
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->set_multiplayer_authority(p_peer_id, true);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->set_multiplayer_authority(p_peer_id, true);
}
}
}
@ -912,10 +923,13 @@ void Node::set_name(const String &p_name) {
if (data.unique_name_in_owner && data.owner) {
_release_unique_name_in_owner();
}
String old_name = data.name;
data.name = name;
if (data.parent) {
data.parent->data.children.erase(old_name);
data.parent->_validate_child_name(this, true);
data.parent->data.children.insert(data.name, this);
}
if (data.unique_name_in_owner && data.owner) {
@ -977,19 +991,8 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) {
//new unique name must be assigned
unique = false;
} else {
//check if exists
Node **children = data.children.ptrw();
int cc = data.children.size();
for (int i = 0; i < cc; i++) {
if (children[i] == p_child) {
continue;
}
if (children[i]->data.name == p_child->data.name) {
unique = false;
break;
}
}
const Node *const *existing = data.children.getptr(p_child->data.name);
unique = !existing || *existing == p_child;
}
if (!unique) {
@ -1053,25 +1056,9 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
name = p_child->get_class();
}
//quickly test if proposed name exists
int cc = data.children.size(); //children count
const Node *const *children_ptr = data.children.ptr();
{
bool exists = false;
for (int i = 0; i < cc; i++) {
if (children_ptr[i] == p_child) { //exclude self in renaming if it's already a child
continue;
}
if (children_ptr[i]->data.name == name) {
exists = true;
}
}
if (!exists) {
return; //if it does not exist, it does not need validation
}
const Node *const *existing = data.children.getptr(name);
if (!existing || *existing == p_child) { // Unused, or is current node.
return;
}
// Extract trailing number
@ -1098,16 +1085,9 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
for (;;) {
StringName attempt = name_string + nums;
bool exists = false;
for (int i = 0; i < cc; i++) {
if (children_ptr[i] == p_child) {
continue;
}
if (children_ptr[i]->data.name == attempt) {
exists = true;
}
}
existing = data.children.getptr(attempt);
bool exists = existing != nullptr && *existing != p_child;
if (!exists) {
name = attempt;
@ -1124,17 +1104,34 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
}
}
void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode) {
//add a child node quickly, without name validation
p_child->data.name = p_name;
p_child->data.index = data.children.size();
data.children.push_back(p_child);
data.children.insert(p_name, p_child);
p_child->data.internal_mode = p_internal_mode;
switch (p_internal_mode) {
case INTERNAL_MODE_FRONT: {
p_child->data.index = data.internal_children_front_count_cache++;
} break;
case INTERNAL_MODE_BACK: {
p_child->data.index = data.internal_children_back_count_cache++;
} break;
case INTERNAL_MODE_DISABLED: {
p_child->data.index = data.external_children_count_cache++;
} break;
}
p_child->data.parent = this;
if (data.internal_children_back > 0) {
_move_child(p_child, data.children.size() - data.internal_children_back - 1);
if (!data.children_cache_dirty && p_internal_mode == INTERNAL_MODE_DISABLED && data.internal_children_back_count_cache == 0) {
// Special case, also add to the cached children array since its cheap.
data.children_cache.push_back(p_child);
} else {
data.children_cache_dirty = true;
}
p_child->notification(NOTIFICATION_PARENTED);
if (data.tree) {
@ -1159,17 +1156,7 @@ void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_i
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, `add_child()` failed. Consider using `add_child.call_deferred(child)` instead.");
_validate_child_name(p_child, p_force_readable_name);
_add_child_nocheck(p_child, p_child->data.name);
if (p_internal == INTERNAL_MODE_FRONT) {
_move_child(p_child, data.internal_children_front);
data.internal_children_front++;
} else if (p_internal == INTERNAL_MODE_BACK) {
if (data.internal_children_back > 0) {
_move_child(p_child, data.children.size() - 1, true);
}
data.internal_children_back++;
}
_add_child_nocheck(p_child, p_child->data.name, p_internal);
}
void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
@ -1178,49 +1165,25 @@ void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, `add_sibling()` failed. Consider using `add_sibling.call_deferred(sibling)` instead.");
InternalMode internal = INTERNAL_MODE_DISABLED;
if (_is_internal_front()) { // The sibling will have the same internal status.
internal = INTERNAL_MODE_FRONT;
} else if (_is_internal_back()) {
internal = INTERNAL_MODE_BACK;
}
data.parent->add_child(p_sibling, p_force_readable_name, internal);
data.parent->add_child(p_sibling, p_force_readable_name, data.internal_mode);
data.parent->_update_children_cache();
data.parent->_move_child(p_sibling, get_index() + 1);
}
void Node::remove_child(Node *p_child) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy adding/removing children, `remove_child()` can't be called at this time. Consider using `remove_child.call_deferred(child)` instead.");
ERR_FAIL_COND(p_child->data.parent != this);
int child_count = data.children.size();
Node **children = data.children.ptrw();
int idx = -1;
if (p_child->data.index >= 0 && p_child->data.index < child_count) {
if (children[p_child->data.index] == p_child) {
idx = p_child->data.index;
}
}
if (idx == -1) { //maybe removed while unparenting or something and index was not updated, so just in case the above fails, try this.
for (int i = 0; i < child_count; i++) {
if (children[i] == p_child) {
idx = i;
break;
}
}
}
ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name()));
//ERR_FAIL_COND( p_child->data.blocked > 0 );
// If internal child, update the counter.
if (p_child->_is_internal_front()) {
data.internal_children_front--;
} else if (p_child->_is_internal_back()) {
data.internal_children_back--;
}
/**
* Do not change the data.internal_children*cache counters here.
* Because if nodes are re-added, the indices can remain
* greater-than-everything indices and children added remain
* properly ordered.
*
* All children indices and counters will be updated next time the
* cache is re-generated.
*/
data.blocked++;
p_child->_set_tree(nullptr);
@ -1228,17 +1191,12 @@ void Node::remove_child(Node *p_child) {
remove_child_notify(p_child);
p_child->notification(NOTIFICATION_UNPARENTED);
data.blocked--;
data.children.remove_at(idx);
//update pointer and size
child_count = data.children.size();
children = data.children.ptrw();
for (int i = idx; i < child_count; i++) {
children[i]->data.index = i;
}
data.children_cache_dirty = true;
bool success = data.children.erase(p_child->data.name);
ERR_FAIL_COND_MSG(!success, "Children name does not match parent name in hashtable, this is a bug.");
notification(NOTIFICATION_CHILD_ORDER_CHANGED);
emit_signal(SNAME("child_order_changed"));
@ -1251,42 +1209,73 @@ void Node::remove_child(Node *p_child) {
}
}
void Node::_update_children_cache_impl() const {
// Assign children
data.children_cache.resize(data.children.size());
int idx = 0;
for (const KeyValue<StringName, Node *> &K : data.children) {
data.children_cache[idx] = K.value;
idx++;
}
// Sort them
data.children_cache.sort_custom<ComparatorByIndex>();
// Update indices
data.external_children_count_cache = 0;
data.internal_children_back_count_cache = 0;
data.internal_children_front_count_cache = 0;
for (uint32_t i = 0; i < data.children_cache.size(); i++) {
switch (data.children_cache[i]->data.internal_mode) {
case INTERNAL_MODE_DISABLED: {
data.children_cache[i]->data.index = data.external_children_count_cache++;
} break;
case INTERNAL_MODE_FRONT: {
data.children_cache[i]->data.index = data.internal_children_front_count_cache++;
} break;
case INTERNAL_MODE_BACK: {
data.children_cache[i]->data.index = data.internal_children_back_count_cache++;
} break;
}
}
data.children_cache_dirty = false;
}
int Node::get_child_count(bool p_include_internal) const {
_update_children_cache();
if (p_include_internal) {
return data.children.size();
return data.children_cache.size();
} else {
return data.children.size() - data.internal_children_front - data.internal_children_back;
return data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache;
}
}
Node *Node::get_child(int p_index, bool p_include_internal) const {
_update_children_cache();
if (p_include_internal) {
if (p_index < 0) {
p_index += data.children.size();
p_index += data.children_cache.size();
}
ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
return data.children[p_index];
ERR_FAIL_INDEX_V(p_index, (int)data.children_cache.size(), nullptr);
return data.children_cache[p_index];
} else {
if (p_index < 0) {
p_index += data.children.size() - data.internal_children_front - data.internal_children_back;
p_index += (int)data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache;
}
ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr);
p_index += data.internal_children_front;
return data.children[p_index];
ERR_FAIL_INDEX_V(p_index, (int)data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache, nullptr);
p_index += data.internal_children_front_count_cache;
return data.children_cache[p_index];
}
}
Node *Node::_get_child_by_name(const StringName &p_name) const {
int cc = data.children.size();
Node *const *cd = data.children.ptr();
for (int i = 0; i < cc; i++) {
if (cd[i]->data.name == p_name) {
return cd[i];
}
const Node *const *node = data.children.getptr(p_name);
if (node) {
return const_cast<Node *>(*node);
} else {
return nullptr;
}
return nullptr;
}
Node *Node::get_node_or_null(const NodePath &p_path) const {
@ -1348,18 +1337,12 @@ Node *Node::get_node_or_null(const NodePath &p_path) const {
} else {
next = nullptr;
for (int j = 0; j < current->data.children.size(); j++) {
Node *child = current->data.children[j];
if (child->data.name == name) {
next = child;
break;
}
}
if (next == nullptr) {
const Node *const *node = current->data.children.getptr(name);
if (node) {
next = const_cast<Node *>(*node);
} else {
return nullptr;
};
}
}
current = next;
}
@ -1402,9 +1385,9 @@ bool Node::has_node(const NodePath &p_path) const {
// Can be recursive or not, and limited to owned nodes.
Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const {
ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr);
Node *const *cptr = data.children.ptr();
int ccount = data.children.size();
_update_children_cache();
Node *const *cptr = data.children_cache.ptr();
int ccount = data.children_cache.size();
for (int i = 0; i < ccount; i++) {
if (p_owned && !cptr[i]->data.owner) {
continue;
@ -1431,9 +1414,9 @@ Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned)
TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const {
TypedArray<Node> ret;
ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret);
Node *const *cptr = data.children.ptr();
int ccount = data.children.size();
_update_children_cache();
Node *const *cptr = data.children_cache.ptr();
int ccount = data.children_cache.size();
for (int i = 0; i < ccount; i++) {
if (p_owned && !cptr[i]->data.owner) {
continue;
@ -1526,6 +1509,8 @@ bool Node::is_greater_than(const Node *p_node) const {
ERR_FAIL_COND_V(data.depth < 0, false);
ERR_FAIL_COND_V(p_node->data.depth < 0, false);
_update_children_cache();
int *this_stack = (int *)alloca(sizeof(int) * data.depth);
int *that_stack = (int *)alloca(sizeof(int) * p_node->data.depth);
@ -1534,15 +1519,16 @@ bool Node::is_greater_than(const Node *p_node) const {
int idx = data.depth - 1;
while (n) {
ERR_FAIL_INDEX_V(idx, data.depth, false);
this_stack[idx--] = n->data.index;
this_stack[idx--] = n->get_index();
n = n->data.parent;
}
ERR_FAIL_COND_V(idx != -1, false);
n = p_node;
idx = p_node->data.depth - 1;
while (n) {
ERR_FAIL_INDEX_V(idx, p_node->data.depth, false);
that_stack[idx--] = n->data.index;
that_stack[idx--] = n->get_index();
n = n->data.parent;
}
@ -1576,8 +1562,8 @@ void Node::get_owned_by(Node *p_by, List<Node *> *p_owned) {
p_owned->push_back(this);
}
for (int i = 0; i < get_child_count(); i++) {
get_child(i)->get_owned_by(p_by, p_owned);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->get_owned_by(p_by, p_owned);
}
}
@ -1895,9 +1881,10 @@ int Node::get_persistent_group_count() const {
void Node::_print_tree_pretty(const String &prefix, const bool last) {
String new_prefix = last ? String::utf8(" ┖╴") : String::utf8(" ┠╴");
print_line(prefix + new_prefix + String(get_name()));
for (int i = 0; i < data.children.size(); i++) {
_update_children_cache();
for (uint32_t i = 0; i < data.children_cache.size(); i++) {
new_prefix = last ? String::utf8(" ") : String::utf8("");
data.children[i]->_print_tree_pretty(prefix + new_prefix, i == data.children.size() - 1);
data.children_cache[i]->_print_tree_pretty(prefix + new_prefix, i == data.children_cache.size() - 1);
}
}
@ -1911,15 +1898,17 @@ void Node::print_tree() {
void Node::_print_tree(const Node *p_node) {
print_line(String(p_node->get_path_to(this)));
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_print_tree(p_node);
_update_children_cache();
for (uint32_t i = 0; i < data.children_cache.size(); i++) {
data.children_cache[i]->_print_tree(p_node);
}
}
void Node::_propagate_reverse_notification(int p_notification) {
data.blocked++;
for (int i = data.children.size() - 1; i >= 0; i--) {
data.children[i]->_propagate_reverse_notification(p_notification);
for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
I->value->_propagate_reverse_notification(p_notification);
}
notification(p_notification, true);
@ -1935,8 +1924,8 @@ void Node::_propagate_deferred_notification(int p_notification, bool p_reverse)
MessageQueue::get_singleton()->push_notification(this, p_notification);
}
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_propagate_deferred_notification(p_notification, p_reverse);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_deferred_notification(p_notification, p_reverse);
}
if (p_reverse) {
@ -1950,8 +1939,8 @@ void Node::propagate_notification(int p_notification) {
data.blocked++;
notification(p_notification);
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->propagate_notification(p_notification);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->propagate_notification(p_notification);
}
data.blocked--;
}
@ -1963,8 +1952,8 @@ void Node::propagate_call(const StringName &p_method, const Array &p_args, const
callv(p_method, p_args);
}
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->propagate_call(p_method, p_args, p_parent_first);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->propagate_call(p_method, p_args, p_parent_first);
}
if (!p_parent_first && has_method(p_method)) {
@ -1980,22 +1969,12 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
}
data.blocked++;
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->_propagate_replace_owner(p_owner, p_by_owner);
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_replace_owner(p_owner, p_by_owner);
}
data.blocked--;
}
int Node::get_index(bool p_include_internal) const {
// p_include_internal = false doesn't make sense if the node is internal.
ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false.");
if (data.parent && !p_include_internal) {
return data.index - data.parent->data.internal_children_front;
}
return data.index;
}
Ref<Tween> Node::create_tween() {
ERR_FAIL_COND_V_MSG(!data.tree, nullptr, "Can't create Tween when not inside scene tree.");
Ref<Tween> tween = get_tree()->create_tween();
@ -2502,7 +2481,7 @@ void Node::replace_by(Node *p_node, bool p_keep_groups) {
}
Node *parent = data.parent;
int index_in_parent = data.index;
int index_in_parent = get_index();
if (data.parent) {
parent->remove_child(this);
@ -2754,8 +2733,8 @@ void Node::get_argument_options(const StringName &p_function, int p_idx, List<St
void Node::clear_internal_tree_resource_paths() {
clear_internal_resource_paths();
for (int i = 0; i < data.children.size(); i++) {
data.children[i]->clear_internal_tree_resource_paths();
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->clear_internal_tree_resource_paths();
}
}
@ -3102,9 +3081,10 @@ Node::~Node() {
data.grouped.clear();
data.owned.clear();
data.children.clear();
data.children_cache.clear();
ERR_FAIL_COND(data.parent);
ERR_FAIL_COND(data.children.size());
ERR_FAIL_COND(data.children_cache.size());
orphan_node_count--;
}

View File

@ -92,6 +92,18 @@ private:
SceneTree::Group *group = nullptr;
};
struct ComparatorByIndex {
bool operator()(const Node *p_left, const Node *p_right) const {
static const uint32_t order[3] = { 1, 0, 2 };
uint32_t order_left = order[p_left->data.internal_mode];
uint32_t order_right = order[p_right->data.internal_mode];
if (order_left == order_right) {
return p_left->data.index < p_right->data.index;
}
return order_left < order_right;
}
};
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
String scene_file_path;
@ -100,13 +112,16 @@ private:
Node *parent = nullptr;
Node *owner = nullptr;
Vector<Node *> children;
HashMap<StringName, Node *> children;
mutable bool children_cache_dirty = true;
mutable LocalVector<Node *> children_cache;
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;
int internal_children_front = 0;
int internal_children_back = 0;
int index = -1;
InternalMode internal_mode = INTERNAL_MODE_DISABLED;
mutable int internal_children_front_count_cache = 0;
mutable int internal_children_back_count_cache = 0;
mutable int external_children_count_cache = 0;
mutable int index = -1; // relative to front, normal or back.
int depth = -1;
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
@ -187,9 +202,6 @@ private:
Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Error _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
_FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.index < data.parent->data.internal_children_front; }
_FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.index >= data.parent->data.children.size() - data.parent->data.internal_children_back; }
friend class SceneTree;
void _set_tree(SceneTree *p_tree);
@ -201,6 +213,14 @@ private:
void _release_unique_name_in_owner();
void _acquire_unique_name_in_owner();
_FORCE_INLINE_ void _update_children_cache() const {
if (unlikely(data.children_cache_dirty)) {
_update_children_cache_impl();
}
}
void _update_children_cache_impl() const;
protected:
void _block() { data.blocked++; }
void _unblock() { data.blocked--; }
@ -219,7 +239,7 @@ protected:
friend class SceneState;
void _add_child_nocheck(Node *p_child, const StringName &p_name);
void _add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode = INTERNAL_MODE_DISABLED);
void _set_owner_nocheck(Node *p_owner);
void _set_name_nocheck(const StringName &p_name);
@ -361,7 +381,31 @@ public:
void set_unique_name_in_owner(bool p_enabled);
bool is_unique_name_in_owner() const;
int get_index(bool p_include_internal = true) const;
_FORCE_INLINE_ int get_index(bool p_include_internal = true) const {
// p_include_internal = false doesn't make sense if the node is internal.
ERR_FAIL_COND_V_MSG(!p_include_internal && data.internal_mode != INTERNAL_MODE_DISABLED, -1, "Node is internal. Can't get index with 'include_internal' being false.");
if (!data.parent) {
return data.index;
}
data.parent->_update_children_cache();
if (!p_include_internal) {
return data.index;
} else {
switch (data.internal_mode) {
case INTERNAL_MODE_DISABLED: {
return data.parent->data.internal_children_front_count_cache + data.index;
} break;
case INTERNAL_MODE_FRONT: {
return data.index;
} break;
case INTERNAL_MODE_BACK: {
return data.parent->data.internal_children_front_count_cache + data.parent->data.external_children_count_cache + data.index;
} break;
}
return -1;
}
}
Ref<Tween> create_tween();