Browse Source

Merge pull request #75627 from reduz/faster-node-child-management

Optimize Node children management
Rémi Verschelde 2 years ago
parent
commit
3683b040ed
2 changed files with 243 additions and 219 deletions
  1. 189 209
      scene/main/node.cpp
  2. 54 10
      scene/main/node.h

+ 189 - 209
scene/main/node.cpp

@@ -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.");
-
-	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--;
-	}
+	ERR_FAIL_COND(p_child->data.parent != this);
+
+	/**
+	 *  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();
+	data.blocked--;
 
-	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--;
 }

+ 54 - 10
scene/main/node.h

@@ -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();