Core: Add Node::iterate_children as a fast way to iterate a node's children, without needing allocations or get_child.

Adds `Iterable` class to templates.
This commit is contained in:
Lukas Tenbrink 2025-06-10 18:43:25 +02:00
parent 6f2ab528ca
commit 175c38d0dc
4 changed files with 101 additions and 13 deletions

46
core/templates/iterable.h Normal file
View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* iterable.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
template <typename I>
class Iterable {
I _begin;
I _end;
public:
I begin() { return _begin; }
I end() { return _end; }
Iterable(I &&begin, I &&end) :
_begin(std::move(begin)), _end(std::move(end)) {}
Iterable(const I &begin, const I &end) :
_begin(begin), _end(end) {}
};

View file

@ -1781,6 +1781,26 @@ void Node::_update_children_cache_impl() const {
data.children_cache_dirty = false; data.children_cache_dirty = false;
} }
template <bool p_include_internal>
Iterable<Node::ChildrenIterator> Node::iterate_children() const {
// The thread guard is omitted for performance reasons.
// ERR_THREAD_GUARD_V(Iterable<ChildrenIterator>(nullptr, nullptr));
_update_children_cache();
const uint32_t size = data.children_cache.size();
// Might be null, but then size and internal counts are also 0.
Node **ptr = data.children_cache.ptr();
if constexpr (p_include_internal) {
return Iterable(ChildrenIterator(ptr), ChildrenIterator(ptr + size));
} else {
return Iterable(ChildrenIterator(ptr + data.internal_children_front_count_cache), ChildrenIterator(ptr + size - data.internal_children_back_count_cache));
}
}
template Iterable<Node::ChildrenIterator> Node::iterate_children<true>() const;
template Iterable<Node::ChildrenIterator> Node::iterate_children<false>() const;
int Node::get_child_count(bool p_include_internal) const { int Node::get_child_count(bool p_include_internal) const {
ERR_THREAD_GUARD_V(0); ERR_THREAD_GUARD_V(0);
if (p_include_internal) { if (p_include_internal) {

View file

@ -31,6 +31,7 @@
#pragma once #pragma once
#include "core/string/node_path.h" #include "core/string/node_path.h"
#include "core/templates/iterable.h"
#include "core/variant/typed_array.h" #include "core/variant/typed_array.h"
#include "scene/main/scene_tree.h" #include "scene/main/scene_tree.h"
#include "scene/scene_string_names.h" #include "scene/scene_string_names.h"
@ -47,8 +48,6 @@ SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t)
class Node : public Object { class Node : public Object {
GDCLASS(Node, Object); GDCLASS(Node, Object);
friend class SceneTreeFTI;
protected: protected:
// During group processing, these are thread-safe. // During group processing, these are thread-safe.
// Outside group processing, these avoid the cost of sync by working as plain primitive types. // Outside group processing, these avoid the cost of sync by working as plain primitive types.
@ -132,6 +131,29 @@ public:
void _update_process(bool p_enable, bool p_for_children); void _update_process(bool p_enable, bool p_for_children);
struct ChildrenIterator {
_FORCE_INLINE_ Node *&operator*() const { return *_ptr; }
_FORCE_INLINE_ Node **operator->() const { return _ptr; }
_FORCE_INLINE_ ChildrenIterator &operator++() {
_ptr++;
return *this;
}
_FORCE_INLINE_ ChildrenIterator &operator--() {
_ptr--;
return *this;
}
_FORCE_INLINE_ bool operator==(const ChildrenIterator &b) const { return _ptr == b._ptr; }
_FORCE_INLINE_ bool operator!=(const ChildrenIterator &b) const { return _ptr != b._ptr; }
ChildrenIterator(Node **p_ptr) { _ptr = p_ptr; }
ChildrenIterator() {}
ChildrenIterator(const ChildrenIterator &p_it) { _ptr = p_it._ptr; }
private:
Node **_ptr = nullptr;
};
private: private:
struct GroupData { struct GroupData {
bool persistent = false; bool persistent = false;
@ -484,6 +506,13 @@ public:
void add_sibling(Node *p_sibling, bool p_force_readable_name = false); void add_sibling(Node *p_sibling, bool p_force_readable_name = false);
void remove_child(Node *p_child); void remove_child(Node *p_child);
/// Optimal way to iterate the children of this node.
/// The caller is responsible to ensure:
/// - The thread has the rights to access the node (is_accessible_from_caller_thread() == true).
/// - No children are inserted, removed, or have their index changed during iteration.
template <bool p_include_internal = true>
Iterable<ChildrenIterator> iterate_children() const;
int get_child_count(bool p_include_internal = true) const; int get_child_count(bool p_include_internal = true) const;
Node *get_child(int p_index, bool p_include_internal = true) const; Node *get_child(int p_index, bool p_include_internal = true) const;
TypedArray<Node> get_children(bool p_include_internal = true) const; TypedArray<Node> get_children(bool p_include_internal = true) const;

View file

@ -465,19 +465,12 @@ void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_half_fra
return; return;
} }
// Temporary direct access to children cache for speed.
// Maybe replaced later by a more generic fast access method
// for children.
p_node->_update_children_cache();
Span<Node *> children = p_node->data.children_cache.span();
uint32_t num_children = children.size();
// Not a Node3D. // Not a Node3D.
// Could be e.g. a viewport or something // Could be e.g. a viewport or something
// so we should still recurse to children. // so we should still recurse to children.
if (!s) { if (!s) {
for (uint32_t n = 0; n < num_children; n++) { for (Node *node : p_node->iterate_children()) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1); _update_dirty_nodes(node, p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
} }
return; return;
} }
@ -592,8 +585,8 @@ void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_half_fra
s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM); s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
// Recurse to children. // Recurse to children.
for (uint32_t n = 0; n < num_children; n++) { for (Node *node : p_node->iterate_children()) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1); _update_dirty_nodes(node, p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
} }
} }