Refactor Node Processing

* Node processing works on the concept of process groups.
* A node group can be inherited, run on main thread, or a sub-thread.
* Groups can be ordered.
* Process priority is now present for physics.

This is the first steps towards implementing https://github.com/godotengine/godot-proposals/issues/6424.
No threading or thread guards exist yet in most of the scene code other than Node. That will have to be added later.
This commit is contained in:
Juan Linietsky 2023-04-10 18:45:53 +02:00
parent cf8ad12b56
commit 98c655ec8d
18 changed files with 1155 additions and 186 deletions

View file

@ -433,6 +433,17 @@ bool CallQueue::is_flushing() const {
return flushing;
}
bool CallQueue::has_messages() const {
if (pages_used == 0) {
return false;
}
if (pages_used == 1 && page_messages[0] == 0) {
return false;
}
return true;
}
int CallQueue::get_max_buffer_usage() const {
return pages.size() * PAGE_SIZE_BYTES;
}

View file

@ -45,6 +45,14 @@ public:
PAGE_SIZE_BYTES = 4096
};
struct Page {
uint8_t data[PAGE_SIZE_BYTES];
};
// Needs to be public to be able to define it outside the class.
// Needs to lock because there can be multiple of these allocators in several threads.
typedef PagedAllocator<Page, true> Allocator;
private:
enum {
TYPE_CALL,
@ -56,12 +64,7 @@ private:
FLAG_MASK = FLAG_NULL_IS_OK - 1,
};
struct Page {
uint8_t data[PAGE_SIZE_BYTES];
};
Mutex mutex;
typedef PagedAllocator<Page, false> Allocator;
Allocator *allocator = nullptr;
bool allocator_is_custom = false;
@ -140,6 +143,8 @@ public:
void clear();
void statistics();
bool has_messages() const;
bool is_flushing() const;
int get_max_buffer_usage() const;

View file

@ -201,6 +201,10 @@ bool Object::_predelete() {
return _predelete_ok;
}
void Object::cancel_free() {
_predelete_ok = false;
}
void Object::_postinitialize() {
_class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize.
_initialize_classv();
@ -1561,6 +1565,7 @@ void Object::_bind_methods() {
ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion);
ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free);
ClassDB::add_virtual_method("Object", MethodInfo("free"), false);

View file

@ -923,6 +923,8 @@ public:
_ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; }
void cancel_free();
Object();
virtual ~Object();
};

View file

@ -105,6 +105,8 @@ public:
// get the ID of the main thread
_FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
_FORCE_INLINE_ static bool is_main_thread() { return caller_id == MAIN_ID; } // Gain a tiny bit of perf here because there is no need to validate caller_id here, because only main thread will be set as 1.
static Error set_name(const String &p_name);
void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings());

View file

@ -97,7 +97,7 @@ private:
}
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
if (elements == nullptr) {
if (elements == nullptr || num_elements == 0) {
return false; // Failed lookups, no elements
}
@ -252,7 +252,7 @@ public:
}
void clear() {
if (elements == nullptr) {
if (elements == nullptr || num_elements == 0) {
return;
}
uint32_t capacity = hash_table_size_primes[capacity_index];

View file

@ -80,7 +80,7 @@ private:
}
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
if (keys == nullptr) {
if (keys == nullptr || num_elements == 0) {
return false; // Failed lookups, no elements
}
@ -237,7 +237,7 @@ public:
}
void clear() {
if (keys == nullptr) {
if (keys == nullptr || num_elements == 0) {
return;
}
uint32_t capacity = hash_table_size_primes[capacity_index];

View file

@ -95,11 +95,13 @@ public:
}
}
void erase(const T &p_val) {
_FORCE_INLINE_ bool erase(const T &p_val) {
int64_t idx = find(p_val);
if (idx >= 0) {
remove_at(idx);
return true;
}
return false;
}
void invert() {

View file

@ -71,12 +71,15 @@ public:
void fill(T p_elem);
void remove_at(int p_index) { _cowdata.remove_at(p_index); }
void erase(const T &p_val) {
_FORCE_INLINE_ bool erase(const T &p_val) {
int idx = find(p_val);
if (idx >= 0) {
remove_at(idx);
return true;
}
return false;
}
void reverse();
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }

View file

@ -182,6 +182,20 @@
[b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs.
</description>
</method>
<method name="call_deferred_thread_group" qualifiers="vararg">
<return type="Variant" />
<param index="0" name="method" type="StringName" />
<description>
This function is similar to [method Object.call_deferred] except that the call will take place when the node thread group is processed. If the node thread group processes in sub-threads, then the call will be done on that thread, right before [constant NOTIFICATION_PROCESS] or [constant NOTIFICATION_PHYSICS_PROCESS], the [method _process] or [method _physics_process] or their internal versions are called.
</description>
</method>
<method name="call_thread_safe" qualifiers="vararg">
<return type="Variant" />
<param index="0" name="method" type="StringName" />
<description>
This function ensures that the calling of this function will succeed, no matter whether it's being done from a thread or not. If called from a thread that is not allowed to call the function, the call will become deferred. Otherwise, the call will go through directly.
</description>
</method>
<method name="can_process" qualifiers="const">
<return type="bool" />
<description>
@ -564,6 +578,20 @@
[b]Note:[/b] Internal children can only be moved within their expected "internal range" (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="notify_deferred_thread_group">
<return type="void" />
<param index="0" name="what" type="int" />
<description>
Similar to [method call_deferred_thread_group], but for notifications.
</description>
</method>
<method name="notify_thread_safe">
<return type="void" />
<param index="0" name="what" type="int" />
<description>
Similar to [method call_thread_safe], but for notifications.
</description>
</method>
<method name="print_orphan_nodes" qualifiers="static">
<return type="void" />
<description>
@ -697,6 +725,14 @@
Sends a [method rpc] to a specific peer identified by [param peer_id] (see [method MultiplayerPeer.set_target_peer]). Returns [code]null[/code].
</description>
</method>
<method name="set_deferred_thread_group">
<return type="void" />
<param index="0" name="property" type="StringName" />
<param index="1" name="value" type="Variant" />
<description>
Similar to [method call_deferred_thread_group], but for setting properties.
</description>
</method>
<method name="set_display_folded">
<return type="void" />
<param index="0" name="fold" type="bool" />
@ -785,6 +821,14 @@
Sets whether this is an instance load placeholder. See [InstancePlaceholder].
</description>
</method>
<method name="set_thread_safe">
<return type="void" />
<param index="0" name="property" type="StringName" />
<param index="1" name="value" type="Variant" />
<description>
Similar to [method call_thread_safe], but for setting properties.
</description>
</method>
<method name="update_configuration_warnings">
<return type="void" />
<description>
@ -811,9 +855,24 @@
<member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0">
Can be used to pause or unpause the node, or make the node paused based on the [SceneTree], or make it inherit the process mode from its parent (default).
</member>
<member name="process_physics_priority" type="int" setter="set_physics_process_priority" getter="get_physics_process_priority" default="0">
Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process] or the internal version.
</member>
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0">
The node's priority in the execution order of the enabled processing callbacks (i.e. [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS] and their internal counterparts). Nodes whose process priority value is [i]lower[/i] will have their processing callbacks executed first.
</member>
<member name="process_thread_group" type="int" setter="set_process_thread_group" getter="get_process_thread_group" enum="Node.ProcessThreadGroup" default="0">
Set the process thread group for this node (basically, whether it receives [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS], [method _process] or [method _physics_process] (and the internal versions) on the main thread or in a sub-thread.
By default, the thread group is [constant PROCESS_THREAD_GROUP_INHERIT], which means that this node belongs to the same thread group as the parent node. The thread groups means that nodes in a specific thread group will process together, separate to other thread groups (depending on [member process_thread_group_order]). If the value is set is [constant PROCESS_THREAD_GROUP_SUB_THREAD], this thread group will occur on a sub thread (not the main thread), otherwise if set to [constant PROCESS_THREAD_GROUP_MAIN_THREAD] it will process on the main thread. If there is not a parent or grandparent node set to something other than inherit, the node will belong to the [i]default thread group[/i]. This default group will process on the main thread and its group order is 0.
During processing in a sub-thread, accessing most functions in nodes outside the thread group is forbidden (and it will result in an error in debug mode). Use [method Object.call_deferred], [method call_thread_safe], [method call_deferred_thread_group] and the likes in order to communicate from the thread groups to the main thread (or to other thread groups).
To better understand process thread groups, the idea is that any node set to any other value than [constant PROCESS_THREAD_GROUP_INHERIT] will include any children (and grandchildren) nodes set to inherit into its process thread group. this means that the processing of all the nodes in the group will happen together, at the same time as the node including them.
</member>
<member name="process_thread_group_order" type="int" setter="set_process_thread_group_order" getter="get_process_thread_group_order">
Change the process thread group order. Groups with a lesser order will process before groups with a greater order. This is useful when a large amount of nodes process in sub thread and, afterwards, another group wants to collect their result in the main thread, as an example.
</member>
<member name="process_thread_messages" type="int" setter="set_process_thread_messages" getter="get_process_thread_messages" enum="Node.ProcessThreadMessages">
Set whether the current thread group will process messages (calls to [method call_deferred_thread_group] on threads, and whether it wants to receive them during regular process or physics process callbacks.
</member>
<member name="scene_file_path" type="String" setter="set_scene_file_path" getter="get_scene_file_path">
If a scene is instantiated from a file, its topmost node contains the absolute file path from which it was loaded in [member scene_file_path] (e.g. [code]res://levels/1.tscn[/code]). Otherwise, [member scene_file_path] is set to an empty string.
</member>
@ -1033,6 +1092,21 @@
<constant name="PROCESS_MODE_DISABLED" value="4" enum="ProcessMode">
Never process. Completely disables processing, ignoring the [SceneTree]'s paused property. This is the inverse of [constant PROCESS_MODE_ALWAYS].
</constant>
<constant name="PROCESS_THREAD_GROUP_INHERIT" value="0" enum="ProcessThreadGroup">
If the [member process_thread_group] property is sent to this, the node will belong to any parent (or grandparent) node that has a thread group mode that is not inherit. See [member process_thread_group] for more information.
</constant>
<constant name="PROCESS_THREAD_GROUP_MAIN_THREAD" value="1" enum="ProcessThreadGroup">
Process this node (and children nodes set to inherit) on the main thread. See [member process_thread_group] for more information.
</constant>
<constant name="PROCESS_THREAD_GROUP_SUB_THREAD" value="2" enum="ProcessThreadGroup">
Process this node (and children nodes set to inherit) on a sub-thread. See [member process_thread_group] for more information.
</constant>
<constant name="FLAG_PROCESS_THREAD_MESSAGES" value="1" enum="ProcessThreadMessages">
</constant>
<constant name="FLAG_PROCESS_THREAD_MESSAGES_PHYSICS" value="2" enum="ProcessThreadMessages">
</constant>
<constant name="FLAG_PROCESS_THREAD_MESSAGES_ALL" value="3" enum="ProcessThreadMessages">
</constant>
<constant name="DUPLICATE_SIGNALS" value="1" enum="DuplicateFlags">
Duplicate the node's signals.
</constant>

View file

@ -354,6 +354,12 @@
Returns [code]true[/code] if the object is allowed to translate messages with [method tr] and [method tr_n]. See also [method set_message_translation].
</description>
</method>
<method name="cancel_free">
<return type="void" />
<description>
If this method is called during [constant NOTIFICATION_PREDELETE], this object will reject being freed and will remain allocated. This is mostly an internal function used for error handling to avoid the user from freeing objects when they are not intended to.
</description>
</method>
<method name="connect">
<return type="int" enum="Error" />
<param index="0" name="signal" type="StringName" />

View file

@ -636,6 +636,8 @@ void EditorNode::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_TREE: {
get_tree()->set_disable_node_threading(true); // No node threading while running editor.
Engine::get_singleton()->set_editor_hint(true);
Window *window = get_window();

View file

@ -178,6 +178,7 @@ static int converter_max_line_length = 100000;
HashMap<Main::CLIScope, Vector<String>> forwardable_cli_arguments;
#endif
static bool single_threaded_scene = false;
bool use_startup_benchmark = false;
String startup_benchmark_file;
@ -423,6 +424,7 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --gpu-abort Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n");
#endif
OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
OS::get_singleton()->print(" --single-threaded-scene Scene tree runs in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n");
#if defined(DEBUG_ENABLED)
OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n");
OS::get_singleton()->print(" --debug-paths Show path lines when running the scene.\n");
@ -1108,6 +1110,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->print("Missing remote debug server uri, aborting.\n");
goto error;
}
} else if (I->get() == "--single-threaded-scene") {
single_threaded_scene = true;
} else if (I->get() == "--build-solutions") { // Build the scripting solution such C#
auto_build_solutions = true;
@ -2780,6 +2784,10 @@ bool Main::start() {
}
#endif
if (single_threaded_scene) {
sml->set_disable_node_threading(true);
}
bool embed_subwindows = GLOBAL_GET("display/window/subwindows/embed_subwindows");
if (single_window || (!project_manager && !editor && embed_subwindows) || !DisplayServer::get_singleton()->has_feature(DisplayServer::Feature::FEATURE_SUBWINDOWS)) {

View file

@ -226,6 +226,11 @@ void Skeleton3D::_update_process_order() {
void Skeleton3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (dirty) {
notification(NOTIFICATION_UPDATE_SKELETON);
}
} break;
case NOTIFICATION_UPDATE_SKELETON: {
RenderingServer *rs = RenderingServer::get_singleton();
Bone *bonesptr = bones.ptrw();
@ -629,7 +634,9 @@ void Skeleton3D::_make_dirty() {
return;
}
MessageQueue::get_singleton()->push_notification(this, NOTIFICATION_UPDATE_SKELETON);
if (is_inside_tree()) {
notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON);
}
dirty = true;
}

View file

@ -47,10 +47,14 @@
#include <stdint.h>
VARIANT_ENUM_CAST(Node::ProcessMode);
VARIANT_ENUM_CAST(Node::ProcessThreadGroup);
VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages);
VARIANT_ENUM_CAST(Node::InternalMode);
int Node::orphan_node_count = 0;
thread_local Node *Node::current_process_thread_group = nullptr;
void Node::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_PROCESS: {
@ -65,6 +69,7 @@ void Node::_notification(int p_notification) {
ERR_FAIL_COND(!get_viewport());
ERR_FAIL_COND(!get_tree());
// Update process mode.
if (data.process_mode == PROCESS_MODE_INHERIT) {
if (data.parent) {
data.process_owner = data.parent->data.process_owner;
@ -77,6 +82,27 @@ void Node::_notification(int p_notification) {
data.process_owner = this;
}
{ // Update threaded process mode.
if (data.process_thread_group == PROCESS_THREAD_GROUP_INHERIT) {
if (data.parent) {
data.process_thread_group_owner = data.parent->data.process_thread_group_owner;
}
if (data.process_thread_group_owner) {
data.process_group = data.process_thread_group_owner->data.process_group;
} else {
data.process_group = &data.tree->default_process_group;
}
} else {
data.process_thread_group_owner = this;
_add_process_group();
}
if (_is_any_processing()) {
_add_to_process_thread_group();
}
}
if (data.input) {
add_to_group("_vp_input" + itos(get_viewport()->get_instance_id()));
}
@ -90,7 +116,7 @@ void Node::_notification(int p_notification) {
add_to_group("_vp_unhandled_key_input" + itos(get_viewport()->get_instance_id()));
}
get_tree()->node_count++;
get_tree()->nodes_in_tree_count++;
orphan_node_count--;
} break;
@ -98,7 +124,7 @@ void Node::_notification(int p_notification) {
ERR_FAIL_COND(!get_viewport());
ERR_FAIL_COND(!get_tree());
get_tree()->node_count--;
get_tree()->nodes_in_tree_count--;
orphan_node_count++;
if (data.input) {
@ -114,7 +140,17 @@ void Node::_notification(int p_notification) {
remove_from_group("_vp_unhandled_key_input" + itos(get_viewport()->get_instance_id()));
}
// Remove from processing first
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
// Remove the process group
if (data.process_thread_group_owner == this) {
_remove_process_group();
}
data.process_thread_group_owner = nullptr;
data.process_owner = nullptr;
if (data.path_cache) {
memdelete(data.path_cache);
data.path_cache = nullptr;
@ -160,6 +196,12 @@ void Node::_notification(int p_notification) {
} break;
case NOTIFICATION_PREDELETE: {
if (data.inside_tree && !Thread::is_main_thread()) {
cancel_free();
ERR_PRINT("Attempted to free a node that is currently added to the SceneTree from a thread. This is not permitted, use queue_free() instead. Node has not been freed.");
return;
}
if (data.parent) {
data.parent->remove_child(this);
}
@ -329,6 +371,7 @@ void Node::_propagate_exit_tree() {
}
void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node.");
@ -440,16 +483,24 @@ void Node::owner_changed_notify() {
}
void Node::set_physics_process(bool p_process) {
ERR_THREAD_GUARD
if (data.physics_process == p_process) {
return;
}
if (!is_inside_tree()) {
data.physics_process = p_process;
return;
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
data.physics_process = p_process;
if (data.physics_process) {
add_to_group(SNAME("_physics_process"), false);
} else {
remove_from_group(SNAME("_physics_process"));
if (_is_any_processing()) {
_add_to_process_thread_group();
}
}
@ -458,16 +509,24 @@ bool Node::is_physics_processing() const {
}
void Node::set_physics_process_internal(bool p_process_internal) {
ERR_THREAD_GUARD
if (data.physics_process_internal == p_process_internal) {
return;
}
if (!is_inside_tree()) {
data.physics_process_internal = p_process_internal;
return;
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
data.physics_process_internal = p_process_internal;
if (data.physics_process_internal) {
add_to_group(SNAME("_physics_process_internal"), false);
} else {
remove_from_group(SNAME("_physics_process_internal"));
if (_is_any_processing()) {
_add_to_process_thread_group();
}
}
@ -476,6 +535,7 @@ bool Node::is_physics_processing_internal() const {
}
void Node::set_process_mode(ProcessMode p_mode) {
ERR_THREAD_GUARD
if (data.process_mode == p_mode) {
return;
}
@ -569,6 +629,7 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int
}
void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
ERR_THREAD_GUARD
data.multiplayer_authority = p_peer_id;
if (p_recursive) {
@ -591,6 +652,7 @@ bool Node::is_multiplayer_authority() const {
/***** RPC CONFIG ********/
void Node::rpc_config(const StringName &p_method, const Variant &p_config) {
ERR_THREAD_GUARD
if (data.rpc_config.get_type() != Variant::DICTIONARY) {
data.rpc_config = Dictionary();
}
@ -762,16 +824,24 @@ double Node::get_process_delta_time() const {
}
void Node::set_process(bool p_process) {
ERR_THREAD_GUARD
if (data.process == p_process) {
return;
}
if (!is_inside_tree()) {
data.process = p_process;
return;
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
data.process = p_process;
if (data.process) {
add_to_group(SNAME("_process"), false);
} else {
remove_from_group(SNAME("_process"));
if (_is_any_processing()) {
_add_to_process_thread_group();
}
}
@ -780,45 +850,118 @@ bool Node::is_processing() const {
}
void Node::set_process_internal(bool p_process_internal) {
ERR_THREAD_GUARD
if (data.process_internal == p_process_internal) {
return;
}
if (!is_inside_tree()) {
data.process_internal = p_process_internal;
return;
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
data.process_internal = p_process_internal;
if (data.process_internal) {
add_to_group(SNAME("_process_internal"), false);
} else {
remove_from_group(SNAME("_process_internal"));
if (_is_any_processing()) {
_add_to_process_thread_group();
}
}
void Node::_add_process_group() {
get_tree()->_add_process_group(this);
}
void Node::_remove_process_group() {
get_tree()->_remove_process_group(this);
}
void Node::_remove_from_process_thread_group() {
get_tree()->_remove_node_from_process_group(this, data.process_thread_group_owner);
}
void Node::_add_to_process_thread_group() {
get_tree()->_add_node_to_process_group(this, data.process_thread_group_owner);
}
void Node::_remove_tree_from_process_thread_group() {
if (!is_inside_tree()) {
return; // May not be initialized yet.
}
for (KeyValue<StringName, Node *> &K : data.children) {
if (K.value->data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) {
continue;
}
K.value->_remove_tree_from_process_thread_group();
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
}
}
void Node::_add_tree_to_process_thread_group(Node *p_owner) {
if (_is_any_processing()) {
_add_to_process_thread_group();
}
data.process_thread_group_owner = p_owner;
if (p_owner != nullptr) {
data.process_group = p_owner->data.process_group;
} else {
data.process_group = &data.tree->default_process_group;
}
for (KeyValue<StringName, Node *> &K : data.children) {
if (K.value->data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) {
continue;
}
K.value->_add_to_process_thread_group();
}
}
bool Node::is_processing_internal() const {
return data.process_internal;
}
void Node::set_process_priority(int p_priority) {
data.process_priority = p_priority;
// Make sure we are in SceneTree.
if (data.tree == nullptr) {
void Node::set_process_thread_group_order(int p_order) {
ERR_THREAD_GUARD
if (data.process_thread_group_order == p_order) {
return;
}
// Make sure we are in SceneTree and an actual process owner
if (!is_inside_tree() || data.process_thread_group_owner != this) {
data.process_thread_group_order = p_order;
return;
}
if (is_processing()) {
data.tree->make_group_changed(SNAME("_process"));
get_tree()->process_groups_dirty = true;
}
int Node::get_process_thread_group_order() const {
return data.process_thread_group_order;
}
void Node::set_process_priority(int p_priority) {
ERR_THREAD_GUARD
if (data.process_priority == p_priority) {
return;
}
// Make sure we are in SceneTree and an actual process owner
if (!is_inside_tree()) {
data.process_priority = p_priority;
return;
}
if (is_processing_internal()) {
data.tree->make_group_changed(SNAME("_process_internal"));
}
if (is_physics_processing()) {
data.tree->make_group_changed(SNAME("_physics_process"));
}
if (is_physics_processing_internal()) {
data.tree->make_group_changed(SNAME("_physics_process_internal"));
if (_is_any_processing()) {
_remove_from_process_thread_group();
data.process_priority = p_priority;
_add_to_process_thread_group();
}
}
@ -826,7 +969,82 @@ int Node::get_process_priority() const {
return data.process_priority;
}
void Node::set_physics_process_priority(int p_priority) {
ERR_THREAD_GUARD
if (data.physics_process_priority == p_priority) {
return;
}
// Make sure we are in SceneTree and an actual physics_process owner
if (!is_inside_tree()) {
data.physics_process_priority = p_priority;
return;
}
if (_is_any_processing()) {
_remove_from_process_thread_group();
data.physics_process_priority = p_priority;
_add_to_process_thread_group();
}
}
int Node::get_physics_process_priority() const {
return data.physics_process_priority;
}
void Node::set_process_thread_group(ProcessThreadGroup p_mode) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Changing the process thread group can only be done from the main thread. Use call_deferred(\"set_process_thread_group\",mode).");
if (data.process_thread_group == p_mode) {
return;
}
if (!is_inside_tree()) {
data.process_thread_group = p_mode;
return;
}
// Mode changed, must update everything.
_remove_tree_from_process_thread_group();
if (data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) {
_remove_process_group();
}
data.process_thread_group = p_mode;
if (p_mode == PROCESS_THREAD_GROUP_INHERIT) {
if (data.parent) {
data.process_thread_group_owner = data.parent->data.process_thread_group_owner;
} else {
data.process_thread_group_owner = nullptr;
}
} else {
data.process_thread_group_owner = this;
_add_process_group();
}
_add_tree_to_process_thread_group(data.process_thread_group_owner);
notify_property_list_changed();
}
Node::ProcessThreadGroup Node::get_process_thread_group() const {
return data.process_thread_group;
}
void Node::set_process_thread_messages(BitField<ProcessThreadMessages> p_flags) {
ERR_THREAD_GUARD
if (data.process_thread_group_order == p_flags) {
return;
}
data.process_thread_messages = p_flags;
}
BitField<Node::ProcessThreadMessages> Node::get_process_thread_messages() const {
return data.process_thread_messages;
}
void Node::set_process_input(bool p_enable) {
ERR_THREAD_GUARD
if (p_enable == data.input) {
return;
}
@ -848,6 +1066,7 @@ bool Node::is_processing_input() const {
}
void Node::set_process_shortcut_input(bool p_enable) {
ERR_THREAD_GUARD
if (p_enable == data.shortcut_input) {
return;
}
@ -868,6 +1087,7 @@ bool Node::is_processing_shortcut_input() const {
}
void Node::set_process_unhandled_input(bool p_enable) {
ERR_THREAD_GUARD
if (p_enable == data.unhandled_input) {
return;
}
@ -888,6 +1108,7 @@ bool Node::is_processing_unhandled_input() const {
}
void Node::set_process_unhandled_key_input(bool p_enable) {
ERR_THREAD_GUARD
if (p_enable == data.unhandled_key_input) {
return;
}
@ -916,6 +1137,7 @@ void Node::_set_name_nocheck(const StringName &p_name) {
}
void Node::set_name(const String &p_name) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Changing the name to nodes inside the SceneTree is only allowed from the main thread. Use call_deferred(\"set_name\",new_name).");
String name = p_name.validate_node_name();
ERR_FAIL_COND(name.is_empty());
@ -1147,6 +1369,9 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalM
}
void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_internal) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Adding children to a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"add_child\",node).");
ERR_THREAD_GUARD
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent
@ -1160,6 +1385,7 @@ void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_i
}
void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Adding a sibling to a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"add_sibling\",node).");
ERR_FAIL_NULL(p_sibling);
ERR_FAIL_NULL(data.parent);
ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself!
@ -1171,6 +1397,7 @@ void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
}
void Node::remove_child(Node *p_child) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Removing children from a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"remove_child\",node).");
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);
@ -1241,6 +1468,7 @@ void Node::_update_children_cache_impl() const {
}
int Node::get_child_count(bool p_include_internal) const {
ERR_THREAD_GUARD_V(0);
_update_children_cache();
if (p_include_internal) {
@ -1251,6 +1479,7 @@ int Node::get_child_count(bool p_include_internal) const {
}
Node *Node::get_child(int p_index, bool p_include_internal) const {
ERR_THREAD_GUARD_V(nullptr);
_update_children_cache();
if (p_include_internal) {
@ -1270,6 +1499,7 @@ Node *Node::get_child(int p_index, bool p_include_internal) const {
}
TypedArray<Node> Node::get_children(bool p_include_internal) const {
ERR_THREAD_GUARD_V(TypedArray<Node>());
TypedArray<Node> arr;
int cc = get_child_count(p_include_internal);
arr.resize(cc);
@ -1290,6 +1520,7 @@ Node *Node::_get_child_by_name(const StringName &p_name) const {
}
Node *Node::get_node_or_null(const NodePath &p_path) const {
ERR_THREAD_GUARD_V(nullptr);
if (p_path.is_empty()) {
return nullptr;
}
@ -1395,6 +1626,7 @@ bool Node::has_node(const NodePath &p_path) const {
// Finds the first child node (in tree order) whose name matches the given pattern.
// 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_THREAD_GUARD_V(nullptr);
ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr);
_update_children_cache();
Node *const *cptr = data.children_cache.ptr();
@ -1423,6 +1655,7 @@ Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned)
// or both (either pattern or type can be left empty).
// Can be recursive or not, and limited to owned nodes.
TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const {
ERR_THREAD_GUARD_V(TypedArray<Node>());
TypedArray<Node> ret;
ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret);
_update_children_cache();
@ -1464,6 +1697,7 @@ TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_ty
}
void Node::reparent(Node *p_parent, bool p_keep_global_transform) {
ERR_THREAD_GUARD
ERR_FAIL_NULL(p_parent);
ERR_FAIL_NULL_MSG(data.parent, "Node needs a parent to be reparented.");
@ -1480,6 +1714,7 @@ Node *Node::get_parent() const {
}
Node *Node::find_parent(const String &p_pattern) const {
ERR_THREAD_GUARD_V(nullptr);
Node *p = data.parent;
while (p) {
if (p->data.name.operator String().match(p_pattern)) {
@ -1492,6 +1727,7 @@ Node *Node::find_parent(const String &p_pattern) const {
}
Window *Node::get_window() const {
ERR_THREAD_GUARD_V(nullptr);
Viewport *vp = get_viewport();
if (vp) {
return vp->get_base_window();
@ -1616,6 +1852,7 @@ void Node::_acquire_unique_name_in_owner() {
}
void Node::set_unique_name_in_owner(bool p_enabled) {
ERR_MAIN_THREAD_GUARD
if (data.unique_name_in_owner == p_enabled) {
return;
}
@ -1637,6 +1874,7 @@ bool Node::is_unique_name_in_owner() const {
}
void Node::set_owner(Node *p_owner) {
ERR_MAIN_THREAD_GUARD
if (data.owner) {
if (data.unique_name_in_owner) {
_release_unique_name_in_owner();
@ -1820,10 +2058,12 @@ NodePath Node::get_path() const {
}
bool Node::is_in_group(const StringName &p_identifier) const {
ERR_THREAD_GUARD_V(false);
return data.grouped.has(p_identifier);
}
void Node::add_to_group(const StringName &p_identifier, bool p_persistent) {
ERR_THREAD_GUARD
ERR_FAIL_COND(!p_identifier.operator String().length());
if (data.grouped.has(p_identifier)) {
@ -1844,6 +2084,7 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) {
}
void Node::remove_from_group(const StringName &p_identifier) {
ERR_THREAD_GUARD
HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier);
if (!E) {
@ -1869,6 +2110,7 @@ TypedArray<StringName> Node::_get_groups() const {
}
void Node::get_groups(List<GroupInfo> *p_groups) const {
ERR_THREAD_GUARD
for (const KeyValue<StringName, GroupData> &E : data.grouped) {
GroupInfo gi;
gi.name = E.key;
@ -1878,6 +2120,7 @@ void Node::get_groups(List<GroupInfo> *p_groups) const {
}
int Node::get_persistent_group_count() const {
ERR_THREAD_GUARD_V(0);
int count = 0;
for (const KeyValue<StringName, GroupData> &E : data.grouped) {
@ -1947,6 +2190,7 @@ void Node::_propagate_deferred_notification(int p_notification, bool p_reverse)
}
void Node::propagate_notification(int p_notification) {
ERR_THREAD_GUARD
data.blocked++;
notification(p_notification);
@ -1957,6 +2201,7 @@ void Node::propagate_notification(int p_notification) {
}
void Node::propagate_call(const StringName &p_method, const Array &p_args, const bool p_parent_first) {
ERR_THREAD_GUARD
data.blocked++;
if (p_parent_first && has_method(p_method)) {
@ -1987,6 +2232,7 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
}
Ref<Tween> Node::create_tween() {
ERR_THREAD_GUARD_V(Ref<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();
tween->bind_node(this);
@ -1994,6 +2240,7 @@ Ref<Tween> Node::create_tween() {
}
void Node::set_scene_file_path(const String &p_scene_file_path) {
ERR_THREAD_GUARD
data.scene_file_path = p_scene_file_path;
}
@ -2002,6 +2249,7 @@ String Node::get_scene_file_path() const {
}
void Node::set_editor_description(const String &p_editor_description) {
ERR_THREAD_GUARD
if (data.editor_description == p_editor_description) {
return;
}
@ -2019,6 +2267,7 @@ String Node::get_editor_description() const {
}
void Node::set_editable_instance(Node *p_node, bool p_editable) {
ERR_THREAD_GUARD
ERR_FAIL_NULL(p_node);
ERR_FAIL_COND(!is_ancestor_of(p_node));
if (!p_editable) {
@ -2040,6 +2289,7 @@ bool Node::is_editable_instance(const Node *p_node) const {
}
Node *Node::get_deepest_editable_node(Node *p_start_node) const {
ERR_THREAD_GUARD_V(nullptr);
ERR_FAIL_NULL_V(p_start_node, nullptr);
ERR_FAIL_COND_V(!is_ancestor_of(p_start_node), p_start_node);
@ -2059,6 +2309,7 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const {
#ifdef TOOLS_ENABLED
void Node::set_property_pinned(const String &p_property, bool p_pinned) {
ERR_THREAD_GUARD
bool current_pinned = false;
Array pinned = get_meta("_edit_pinned_properties_", Array());
StringName psa = get_property_store_alias(p_property);
@ -2096,6 +2347,7 @@ bool Node::is_part_of_edited_scene() const {
#endif
void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) const {
ERR_THREAD_GUARD
List<PropertyInfo> pi;
get_property_list(&pi);
for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) {
@ -2106,6 +2358,7 @@ void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) c
}
String Node::to_string() {
ERR_THREAD_GUARD_V(String());
if (get_script_instance()) {
bool valid;
String ret = get_script_instance()->to_string(&valid);
@ -2118,6 +2371,7 @@ String Node::to_string() {
}
void Node::set_scene_instance_state(const Ref<SceneState> &p_state) {
ERR_THREAD_GUARD
data.instance_state = p_state;
}
@ -2126,6 +2380,7 @@ Ref<SceneState> Node::get_scene_instance_state() const {
}
void Node::set_scene_inherited_state(const Ref<SceneState> &p_state) {
ERR_THREAD_GUARD
data.inherited_state = p_state;
}
@ -2142,6 +2397,7 @@ bool Node::get_scene_instance_load_placeholder() const {
}
Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) const {
ERR_THREAD_GUARD_V(nullptr);
Node *node = nullptr;
bool instantiated = false;
@ -2323,6 +2579,7 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
}
Node *Node::duplicate(int p_flags) const {
ERR_THREAD_GUARD_V(nullptr);
Node *dupe = _duplicate(p_flags);
if (dupe && (p_flags & DUPLICATE_SIGNALS)) {
@ -2467,6 +2724,7 @@ static void find_owned_by(Node *p_by, Node *p_node, List<Node *> *p_owned) {
}
void Node::replace_by(Node *p_node, bool p_keep_groups) {
ERR_THREAD_GUARD
ERR_FAIL_NULL(p_node);
ERR_FAIL_COND(p_node->data.parent);
@ -2536,6 +2794,7 @@ void Node::_replace_connections_target(Node *p_new_target) {
}
bool Node::has_node_and_resource(const NodePath &p_path) const {
ERR_THREAD_GUARD_V(false);
if (!has_node(p_path)) {
return false;
}
@ -2570,6 +2829,7 @@ Array Node::_get_node_and_resource(const NodePath &p_path) {
}
Node *Node::get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const {
ERR_THREAD_GUARD_V(nullptr);
Node *node = get_node(p_path);
r_res = Ref<Resource>();
r_leftover_subpath = Vector<StringName>();
@ -2739,6 +2999,7 @@ void Node::clear_internal_tree_resource_paths() {
}
PackedStringArray Node::get_configuration_warnings() const {
ERR_THREAD_GUARD_V(PackedStringArray());
PackedStringArray ret;
Vector<String> warnings;
@ -2764,6 +3025,7 @@ String Node::get_configuration_warnings_as_string() const {
}
void Node::update_configuration_warnings() {
ERR_THREAD_GUARD
#ifdef TOOLS_ENABLED
if (!is_inside_tree()) {
return;
@ -2779,6 +3041,7 @@ bool Node::is_owned_by_parent() const {
}
void Node::set_display_folded(bool p_folded) {
ERR_THREAD_GUARD
data.display_folded = p_folded;
}
@ -2791,6 +3054,7 @@ bool Node::is_ready() const {
}
void Node::request_ready() {
ERR_THREAD_GUARD
data.ready_first = true;
}
@ -2834,6 +3098,12 @@ void Node::_call_unhandled_key_input(const Ref<InputEvent> &p_event) {
unhandled_key_input(p_event);
}
void Node::_validate_property(PropertyInfo &p_property) const {
if ((p_property.name == "process_thread_group_order" || p_property.name == "process_thread_messages") && data.process_thread_group == PROCESS_THREAD_GROUP_INHERIT) {
p_property.usage = 0;
}
}
void Node::input(const Ref<InputEvent> &p_event) {
}
@ -2846,6 +3116,94 @@ void Node::unhandled_input(const Ref<InputEvent> &p_event) {
void Node::unhandled_key_input(const Ref<InputEvent> &p_key_event) {
}
Variant Node::_call_deferred_thread_group_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 0;
return Variant();
}
if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
return Variant();
}
r_error.error = Callable::CallError::CALL_OK;
StringName method = *p_args[0];
call_deferred_thread_groupp(method, &p_args[1], p_argcount - 1, true);
return Variant();
}
Variant Node::_call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 0;
return Variant();
}
if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
return Variant();
}
r_error.error = Callable::CallError::CALL_OK;
StringName method = *p_args[0];
call_thread_safep(method, &p_args[1], p_argcount - 1, true);
return Variant();
}
void Node::call_deferred_thread_groupp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
ERR_FAIL_COND(!is_inside_tree());
SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group;
pg->call_queue.push_callp(this, p_method, p_args, p_argcount, p_show_error);
}
void Node::set_deferred_thread_group(const StringName &p_property, const Variant &p_value) {
ERR_FAIL_COND(!is_inside_tree());
SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group;
pg->call_queue.push_set(this, p_property, p_value);
}
void Node::notify_deferred_thread_group(int p_notification) {
ERR_FAIL_COND(!is_inside_tree());
SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group;
pg->call_queue.push_notification(this, p_notification);
}
void Node::call_thread_safep(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
if (is_accessible_from_caller_thread()) {
Callable::CallError ce;
callp(p_method, p_args, p_argcount, ce);
if (p_show_error && ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_MSG("Error calling method from 'call_threadp': " + Variant::get_call_error_text(this, p_method, p_args, p_argcount, ce) + ".");
}
} else {
call_deferred_thread_groupp(p_method, p_args, p_argcount, p_show_error);
}
}
void Node::set_thread_safe(const StringName &p_property, const Variant &p_value) {
if (is_accessible_from_caller_thread()) {
set(p_property, p_value);
} else {
set_deferred_thread_group(p_property, p_value);
}
}
void Node::notify_thread_safe(int p_notification) {
if (is_accessible_from_caller_thread()) {
notification(p_notification);
} else {
notify_deferred_thread_group(p_notification);
}
}
void Node::_bind_methods() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/naming/node_name_num_separator", PROPERTY_HINT_ENUM, "None,Space,Underscore,Dash"), 0);
GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/naming/node_name_casing", PROPERTY_HINT_ENUM, "PascalCase,camelCase,snake_case"), NAME_CASING_PASCAL_CASE);
@ -2897,6 +3255,8 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_process", "enable"), &Node::set_process);
ClassDB::bind_method(D_METHOD("set_process_priority", "priority"), &Node::set_process_priority);
ClassDB::bind_method(D_METHOD("get_process_priority"), &Node::get_process_priority);
ClassDB::bind_method(D_METHOD("set_physics_process_priority", "priority"), &Node::set_physics_process_priority);
ClassDB::bind_method(D_METHOD("get_physics_process_priority"), &Node::get_physics_process_priority);
ClassDB::bind_method(D_METHOD("is_processing"), &Node::is_processing);
ClassDB::bind_method(D_METHOD("set_process_input", "enable"), &Node::set_process_input);
ClassDB::bind_method(D_METHOD("is_processing_input"), &Node::is_processing_input);
@ -2910,6 +3270,15 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_process_mode"), &Node::get_process_mode);
ClassDB::bind_method(D_METHOD("can_process"), &Node::can_process);
ClassDB::bind_method(D_METHOD("set_process_thread_group", "mode"), &Node::set_process_thread_group);
ClassDB::bind_method(D_METHOD("get_process_thread_group"), &Node::get_process_thread_group);
ClassDB::bind_method(D_METHOD("set_process_thread_messages", "flags"), &Node::set_process_thread_messages);
ClassDB::bind_method(D_METHOD("get_process_thread_messages"), &Node::get_process_thread_messages);
ClassDB::bind_method(D_METHOD("set_process_thread_group_order", "order"), &Node::set_process_thread_group_order);
ClassDB::bind_method(D_METHOD("get_process_thread_group_order"), &Node::get_process_thread_group_order);
ClassDB::bind_method(D_METHOD("set_display_folded", "fold"), &Node::set_display_folded);
ClassDB::bind_method(D_METHOD("is_displayed_folded"), &Node::is_displayed_folded);
@ -2977,6 +3346,26 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("update_configuration_warnings"), &Node::update_configuration_warnings);
{
MethodInfo mi;
mi.name = "call_deferred_thread_group";
mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_deferred_thread_group", &Node::_call_deferred_thread_group_bind, mi, varray(), false);
}
ClassDB::bind_method(D_METHOD("set_deferred_thread_group", "property", "value"), &Node::set_deferred_thread_group);
ClassDB::bind_method(D_METHOD("notify_deferred_thread_group", "what"), &Node::notify_deferred_thread_group);
{
MethodInfo mi;
mi.name = "call_thread_safe";
mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_thread_safe", &Node::_call_thread_safe_bind, mi, varray(), false);
}
ClassDB::bind_method(D_METHOD("set_thread_safe", "property", "value"), &Node::set_thread_safe);
ClassDB::bind_method(D_METHOD("notify_thread_safe", "what"), &Node::notify_thread_safe);
BIND_CONSTANT(NOTIFICATION_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_EXIT_TREE);
BIND_CONSTANT(NOTIFICATION_MOVED_IN_PARENT);
@ -3029,6 +3418,14 @@ void Node::_bind_methods() {
BIND_ENUM_CONSTANT(PROCESS_MODE_ALWAYS);
BIND_ENUM_CONSTANT(PROCESS_MODE_DISABLED);
BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_INHERIT);
BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_MAIN_THREAD);
BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_SUB_THREAD);
BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES);
BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS);
BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES_ALL);
BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS);
BIND_ENUM_CONSTANT(DUPLICATE_GROUPS);
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
@ -3056,6 +3453,11 @@ void Node::_bind_methods() {
ADD_GROUP("Process", "process_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Inherit,Pausable,When Paused,Always,Disabled"), "set_process_mode", "get_process_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_physics_priority"), "set_physics_process_priority", "get_physics_process_priority");
ADD_SUBGROUP("Thread Group", "process_thread");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group", PROPERTY_HINT_ENUM, "Inherit,Main Thread,Sub Thread"), "set_process_thread_group", "get_process_thread_group");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages");
ADD_GROUP("Editor Description", "editor_");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT), "set_editor_description", "get_editor_description");

View file

@ -54,6 +54,18 @@ public:
PROCESS_MODE_DISABLED, // never process
};
enum ProcessThreadGroup {
PROCESS_THREAD_GROUP_INHERIT,
PROCESS_THREAD_GROUP_MAIN_THREAD,
PROCESS_THREAD_GROUP_SUB_THREAD,
};
enum ProcessThreadMessages {
FLAG_PROCESS_THREAD_MESSAGES = 1,
FLAG_PROCESS_THREAD_MESSAGES_PHYSICS = 2,
FLAG_PROCESS_THREAD_MESSAGES_ALL = 3,
};
enum DuplicateFlags {
DUPLICATE_SIGNALS = 1,
DUPLICATE_GROUPS = 2,
@ -80,12 +92,10 @@ public:
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
};
struct ComparatorWithPriority {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; }
};
static int orphan_node_count;
void _update_process(bool p_enable, bool p_for_children);
private:
struct GroupData {
bool persistent = false;
@ -104,6 +114,14 @@ private:
}
};
struct ComparatorWithPriority {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; }
};
struct ComparatorWithPhysicsPriority {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.physics_process_priority == p_a->data.physics_process_priority ? p_b->is_greater_than(p_a) : p_b->data.physics_process_priority > p_a->data.physics_process_priority; }
};
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
String scene_file_path;
@ -142,6 +160,11 @@ private:
ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT;
Node *process_thread_group_owner = nullptr;
int process_thread_group_order = 0;
BitField<ProcessThreadMessages> process_thread_messages;
void *process_group = nullptr; // to avoid cyclic dependency
int multiplayer_authority = 1; // Server by default.
Variant rpc_config;
@ -151,6 +174,7 @@ private:
bool physics_process = false;
bool process = false;
int process_priority = 0;
int physics_process_priority = 0;
bool physics_process_internal = false;
bool process_internal = false;
@ -220,6 +244,19 @@ private:
void _update_children_cache_impl() const;
// Process group management
void _add_process_group();
void _remove_process_group();
void _add_to_process_thread_group();
void _remove_from_process_thread_group();
void _remove_tree_from_process_thread_group();
void _add_tree_to_process_thread_group(Node *p_owner);
static thread_local Node *current_process_thread_group;
Variant _call_deferred_thread_group_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
protected:
void _block() { data.blocked++; }
void _unblock() { data.blocked--; }
@ -248,6 +285,8 @@ protected:
void _call_unhandled_input(const Ref<InputEvent> &p_event);
void _call_unhandled_key_input(const Ref<InputEvent> &p_event);
void _validate_property(PropertyInfo &p_property) const;
protected:
virtual void input(const Ref<InputEvent> &p_event);
virtual void shortcut_input(const Ref<InputEvent> &p_key_event);
@ -456,6 +495,12 @@ public:
void set_process_priority(int p_priority);
int get_process_priority() const;
void set_process_thread_group_order(int p_order);
int get_process_thread_group_order() const;
void set_physics_process_priority(int p_priority);
int get_physics_process_priority() const;
void set_process_input(bool p_enable);
bool is_processing_input() const;
@ -468,6 +513,23 @@ public:
void set_process_unhandled_key_input(bool p_enable);
bool is_processing_unhandled_key_input() const;
_FORCE_INLINE_ bool _is_any_processing() const {
return data.process || data.process_internal || data.physics_process || data.physics_process_internal;
}
_FORCE_INLINE_ bool is_accessible_from_caller_thread() const {
if (current_process_thread_group == nullptr) {
// Not thread processing. Only accessible if node is outside the scene tree,
// or if accessing from the main thread.
return !data.inside_tree || Thread::is_main_thread();
} else {
// Thread processing
return current_process_thread_group == data.process_thread_group_owner;
}
}
void set_process_thread_messages(BitField<ProcessThreadMessages> p_flags);
BitField<ProcessThreadMessages> get_process_thread_messages() const;
Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const;
#ifdef TOOLS_ENABLED
Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const;
@ -499,10 +561,13 @@ public:
bool can_process() const;
bool can_process_notification(int p_what) const;
bool is_enabled() const;
bool is_ready() const;
void request_ready();
void set_process_thread_group(ProcessThreadGroup p_mode);
ProcessThreadGroup get_process_thread_group() const;
static void print_orphan_nodes();
#ifdef TOOLS_ENABLED
@ -554,6 +619,32 @@ public:
Ref<MultiplayerAPI> get_multiplayer() const;
void call_deferred_thread_groupp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
void call_deferred_thread_group(const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
void set_deferred_thread_group(const StringName &p_property, const Variant &p_value);
void notify_deferred_thread_group(int p_notification);
void call_thread_safep(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
void call_thread_safe(const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
void set_thread_safe(const StringName &p_property, const Variant &p_value);
void notify_thread_safe(int p_notification);
Node();
~Node();
};
@ -580,6 +671,18 @@ Error Node::rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args)
return rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
#ifdef DEBUG_ENABLED
#define ERR_THREAD_GUARD ERR_FAIL_COND_MSG(!is_accessible_from_caller_thread(), "Caller thread can't call this function in this node. Use call_deferred() or call_thread_group() instead.");
#define ERR_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(!is_accessible_from_caller_thread(), (m_ret), "Caller thread can't call this function in this node. Use call_deferred() or call_thread_group() instead.")
#define ERR_MAIN_THREAD_GUARD ERR_FAIL_COND_MSG(is_inside_tree() && !Thread::is_main_thread(), "This function in this node can only be accessed from the main thread. Use call_deferred() instead.");
#define ERR_MAIN_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(is_inside_tree() && !Thread::is_main_thread(), (m_ret), "This function in this node can only be accessed from the main thread. Use call_deferred() instead.")
#else
#define ERR_THREAD_GUARD
#define ERR_THREAD_GUARD_V(m_ret)
#define ERR_MAIN_THREAD_GUARD
#define ERR_MAIN_THREAD_GUARD_V(m_ret)
#endif
// Add these macro to your class's 'get_configuration_warnings' function to have warnings show up in the scene tree inspector.
#define DEPRECATED_NODE_WARNING warnings.push_back(RTR("This node is marked as deprecated and will be removed in future versions.\nPlease check the Godot documentation for information about migration."));
#define EXPERIMENTAL_NODE_WARNING warnings.push_back(RTR("This node is marked as experimental and may be subject to removal or major changes in future versions."));

View file

@ -38,6 +38,7 @@
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/object/message_queue.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
@ -60,7 +61,6 @@
#include "servers/physics_server_2d.h"
#include "servers/physics_server_3d.h"
#include "window.h"
#include <stdio.h>
#include <stdlib.h>
@ -126,12 +126,13 @@ void SceneTree::node_added(Node *p_node) {
}
void SceneTree::node_removed(Node *p_node) {
// Nodes can only be removed from the main thread.
if (current_scene == p_node) {
current_scene = nullptr;
}
emit_signal(node_removed_name, p_node);
if (call_lock > 0) {
call_skip.insert(p_node);
if (nodes_removed_on_group_call_lock) {
nodes_removed_on_group_call.insert(p_node);
}
}
@ -140,6 +141,8 @@ void SceneTree::node_renamed(Node *p_node) {
}
SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_node) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
E = group_map.insert(p_group, Group());
@ -153,6 +156,8 @@ SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_nod
}
void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
ERR_FAIL_COND(!E);
@ -163,6 +168,7 @@ void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) {
}
void SceneTree::make_group_changed(const StringName &p_group) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (E) {
E->value.changed = true;
@ -170,6 +176,8 @@ void SceneTree::make_group_changed(const StringName &p_group) {
}
void SceneTree::flush_transform_notifications() {
_THREAD_SAFE_METHOD_
SelfList<Node> *n = xform_change_list.first();
while (n) {
Node *node = n->self();
@ -200,7 +208,7 @@ void SceneTree::_flush_ugc() {
ugc_locked = false;
}
void SceneTree::_update_group_order(Group &g, bool p_use_priority) {
void SceneTree::_update_group_order(Group &g) {
if (!g.changed) {
return;
}
@ -211,57 +219,62 @@ void SceneTree::_update_group_order(Group &g, bool p_use_priority) {
Node **gr_nodes = g.nodes.ptrw();
int gr_node_count = g.nodes.size();
if (p_use_priority) {
SortArray<Node *, Node::ComparatorWithPriority> node_sort;
node_sort.sort(gr_nodes, gr_node_count);
} else {
SortArray<Node *, Node::Comparator> node_sort;
node_sort.sort(gr_nodes, gr_node_count);
}
SortArray<Node *, Node::Comparator> node_sort;
node_sort.sort(gr_nodes, gr_node_count);
g.changed = false;
}
void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_group, const StringName &p_function, const Variant **p_args, int p_argcount) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
Vector<Node *> nodes_copy;
if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) {
ERR_FAIL_COND(ugc_locked);
{
_THREAD_SAFE_METHOD_
UGCall ug;
ug.call = p_function;
ug.group = p_group;
if (unique_group_calls.has(ug)) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
Vector<Variant> args;
for (int i = 0; i < p_argcount; i++) {
args.push_back(*p_args[i]);
if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) {
ERR_FAIL_COND(ugc_locked);
UGCall ug;
ug.call = p_function;
ug.group = p_group;
if (unique_group_calls.has(ug)) {
return;
}
Vector<Variant> args;
for (int i = 0; i < p_argcount; i++) {
args.push_back(*p_args[i]);
}
unique_group_calls[ug] = args;
return;
}
unique_group_calls[ug] = args;
return;
_update_group_order(g);
nodes_copy = g.nodes;
}
_update_group_order(g);
Vector<Node *> nodes_copy = g.nodes;
Node **gr_nodes = nodes_copy.ptrw();
int gr_node_count = nodes_copy.size();
call_lock++;
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock++;
}
if (p_call_flags & GROUP_CALL_REVERSE) {
for (int i = gr_node_count - 1; i >= 0; i--) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call_lock && nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -275,7 +288,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro
} else {
for (int i = 0; i < gr_node_count; i++) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call_lock && nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -288,33 +301,44 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro
}
}
call_lock--;
if (call_lock == 0) {
call_skip.clear();
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock--;
if (nodes_removed_on_group_call_lock == 0) {
nodes_removed_on_group_call.clear();
}
}
}
void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
Vector<Node *> nodes_copy;
{
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
_update_group_order(g);
nodes_copy = g.nodes;
}
_update_group_order(g);
Vector<Node *> nodes_copy = g.nodes;
Node **gr_nodes = nodes_copy.ptrw();
int gr_node_count = nodes_copy.size();
call_lock++;
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock++;
}
if (p_call_flags & GROUP_CALL_REVERSE) {
for (int i = gr_node_count - 1; i >= 0; i--) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -327,7 +351,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr
} else {
for (int i = 0; i < gr_node_count; i++) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -339,33 +363,44 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr
}
}
call_lock--;
if (call_lock == 0) {
call_skip.clear();
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock--;
if (nodes_removed_on_group_call_lock == 0) {
nodes_removed_on_group_call.clear();
}
}
}
void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
Vector<Node *> nodes_copy;
{
_THREAD_SAFE_METHOD_
_update_group_order(g);
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
Vector<Node *> nodes_copy = g.nodes;
_update_group_order(g);
nodes_copy = g.nodes;
}
Node **gr_nodes = nodes_copy.ptrw();
int gr_node_count = nodes_copy.size();
call_lock++;
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock++;
}
if (p_call_flags & GROUP_CALL_REVERSE) {
for (int i = gr_node_count - 1; i >= 0; i--) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -378,7 +413,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group
} else {
for (int i = 0; i < gr_node_count; i++) {
if (call_lock && call_skip.has(gr_nodes[i])) {
if (nodes_removed_on_group_call.has(gr_nodes[i])) {
continue;
}
@ -390,9 +425,12 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group
}
}
call_lock--;
if (call_lock == 0) {
call_skip.clear();
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock--;
if (nodes_removed_on_group_call_lock == 0) {
nodes_removed_on_group_call.clear();
}
}
}
@ -423,9 +461,10 @@ bool SceneTree::physics_process(double p_time) {
emit_signal(SNAME("physics_frame"));
_notify_group_pause(SNAME("_physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
call_group(SNAME("_picking_viewports"), SNAME("_process_picking"));
_notify_group_pause(SNAME("_physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS);
_process(true);
_flush_ugc();
MessageQueue::get_singleton()->flush(); //small little hack
@ -462,8 +501,7 @@ bool SceneTree::process(double p_time) {
flush_transform_notifications();
_notify_group_pause(SNAME("_process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS);
_notify_group_pause(SNAME("_process"), Node::NOTIFICATION_PROCESS);
_process(false);
_flush_ugc();
MessageQueue::get_singleton()->flush(); //small little hack
@ -512,6 +550,7 @@ bool SceneTree::process(double p_time) {
}
void SceneTree::process_timers(double p_delta, bool p_physics_frame) {
_THREAD_SAFE_METHOD_
List<Ref<SceneTreeTimer>>::Element *L = timers.back(); //last element
for (List<Ref<SceneTreeTimer>>::Element *E = timers.front(); E;) {
@ -544,6 +583,7 @@ void SceneTree::process_timers(double p_delta, bool p_physics_frame) {
}
void SceneTree::process_tweens(double p_delta, bool p_physics) {
_THREAD_SAFE_METHOD_
// This methods works similarly to how SceneTreeTimers are handled.
List<Ref<Tween>>::Element *L = tweens.back();
@ -603,6 +643,8 @@ void SceneTree::finalize() {
}
void SceneTree::quit(int p_exit_code) {
_THREAD_SAFE_METHOD_
OS::get_singleton()->set_exit_code(p_exit_code);
_quit = true;
}
@ -730,6 +772,8 @@ float SceneTree::get_debug_paths_width() const {
}
Ref<Material> SceneTree::get_debug_paths_material() {
_THREAD_SAFE_METHOD_
if (debug_paths_material.is_valid()) {
return debug_paths_material;
}
@ -747,6 +791,8 @@ Ref<Material> SceneTree::get_debug_paths_material() {
}
Ref<Material> SceneTree::get_debug_collision_material() {
_THREAD_SAFE_METHOD_
if (collision_material.is_valid()) {
return collision_material;
}
@ -764,6 +810,8 @@ Ref<Material> SceneTree::get_debug_collision_material() {
}
Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() {
_THREAD_SAFE_METHOD_
if (debug_contact_mesh.is_valid()) {
return debug_contact_mesh;
}
@ -821,6 +869,8 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() {
}
void SceneTree::set_pause(bool p_enabled) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Pause can only be set from the main thread.");
if (p_enabled == paused) {
return;
}
@ -836,70 +886,282 @@ bool SceneTree::is_paused() const {
return paused;
}
void SceneTree::_notify_group_pause(const StringName &p_group, int p_notification) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
void SceneTree::_process_group(ProcessGroup *p_group, bool p_physics) {
// When reading this function, keep in mind that this code must work in a way where
// if any node is removed, this needs to continue working.
p_group->call_queue.flush(); // Flush messages before processing.
Vector<Node *> &nodes = p_physics ? p_group->physics_nodes : p_group->nodes;
if (nodes.is_empty()) {
return;
}
_update_group_order(g, p_notification == Node::NOTIFICATION_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PROCESS || p_notification == Node::NOTIFICATION_PHYSICS_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
bool &node_order_dirty = p_physics ? p_group->physics_node_order_dirty : p_group->node_order_dirty;
//copy, so copy on write happens in case something is removed from process while being called
//performance is not lost because only if something is added/removed the vector is copied.
Vector<Node *> nodes_copy = g.nodes;
int gr_node_count = nodes_copy.size();
Node **gr_nodes = nodes_copy.ptrw();
call_lock++;
for (int i = 0; i < gr_node_count; i++) {
Node *n = gr_nodes[i];
if (call_lock && call_skip.has(n)) {
continue;
}
if (!n->can_process()) {
continue;
}
if (!n->can_process_notification(p_notification)) {
continue;
}
n->notification(p_notification);
//ERR_FAIL_COND(gr_node_count != g.nodes.size());
if (node_order_dirty) {
nodes.sort_custom<Node::ComparatorWithPhysicsPriority>();
node_order_dirty = false;
}
call_lock--;
if (call_lock == 0) {
call_skip.clear();
// Make a copy, so if nodes are added/removed from process, this does not break
Vector<Node *> nodes_copy = nodes;
uint32_t node_count = nodes_copy.size();
Node **nodes_ptr = (Node **)nodes_copy.ptr(); // Force cast, pointer will not change.
for (uint32_t i = 0; i < node_count; i++) {
Node *n = nodes_ptr[i];
if (nodes_removed_on_group_call.has(n)) {
// Node may have been removed during process, skip it.
// Keep in mind removals can only happen on the main thread.
continue;
}
if (!n->can_process() || !n->is_inside_tree()) {
continue;
}
if (p_physics) {
if (n->is_physics_processing()) {
n->notification(Node::NOTIFICATION_PHYSICS_PROCESS);
}
if (n->is_physics_processing_internal()) {
n->notification(Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
}
} else {
if (n->is_processing()) {
n->notification(Node::NOTIFICATION_PROCESS);
}
if (n->is_processing_internal()) {
n->notification(Node::NOTIFICATION_INTERNAL_PROCESS);
}
}
}
p_group->call_queue.flush(); // Flush messages also after processing (for potential deferred calls).
}
void SceneTree::_process_groups_thread(uint32_t p_index, bool p_physics) {
Node::current_process_thread_group = local_process_group_cache[p_index]->owner;
_process_group(local_process_group_cache[p_index], p_physics);
Node::current_process_thread_group = nullptr;
}
void SceneTree::_process(bool p_physics) {
if (process_groups_dirty) {
{
// First, remove dirty groups.
// This needs to be done when not processing to avoid problems.
ProcessGroup **pg_ptr = (ProcessGroup **)process_groups.ptr(); // discard constness.
uint32_t pg_count = process_groups.size();
for (uint32_t i = 0; i < pg_count; i++) {
if (pg_ptr[i]->removed) {
// Replace removed with last.
pg_ptr[i] = pg_ptr[pg_count - 1];
// Retry
i--;
pg_count--;
}
}
if (pg_count != process_groups.size()) {
process_groups.resize(pg_count);
}
}
{
// Then, re-sort groups.
process_groups.sort_custom<ProcessGroupSort>();
}
process_groups_dirty = false;
}
// Cache the group count, because during processing new groups may be added.
// They will be added at the end, hence for consistency they will be ignored by this process loop.
// No group will be removed from the array during processing (this is done earlier in this function by marking the groups dirty).
uint32_t group_count = process_groups.size();
if (group_count == 0) {
return;
}
process_last_pass++; // Increment pass
uint32_t from = 0;
uint32_t process_count = 0;
nodes_removed_on_group_call_lock++;
int current_order = process_groups[0]->owner ? process_groups[0]->owner->data.process_thread_group_order : 0;
bool current_threaded = process_groups[0]->owner ? process_groups[0]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD : false;
for (uint32_t i = 0; i <= group_count; i++) {
int order = i < group_count && process_groups[i]->owner ? process_groups[i]->owner->data.process_thread_group_order : 0;
bool threaded = i < group_count && process_groups[i]->owner ? process_groups[i]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD : false;
if (i == group_count || current_order != order || current_threaded != threaded) {
if (process_count > 0) {
// Proceed to process the group.
bool using_threads = process_groups[from]->owner && process_groups[from]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD && !node_threading_disabled;
if (using_threads) {
local_process_group_cache.clear();
}
for (uint32_t j = from; j < i; j++) {
if (process_groups[j]->last_pass == process_last_pass) {
if (using_threads) {
local_process_group_cache.push_back(process_groups[j]);
} else {
_process_group(process_groups[j], p_physics);
}
}
}
if (using_threads) {
WorkerThreadPool::GroupID id = WorkerThreadPool::get_singleton()->add_template_group_task(this, &SceneTree::_process_groups_thread, p_physics, local_process_group_cache.size(), -1, true);
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(id);
}
}
if (i == group_count) {
// This one is invalid, no longer process
break;
}
from = i;
current_threaded = threaded;
current_order = order;
}
if (process_groups[i]->removed) {
continue;
}
ProcessGroup *pg = process_groups[i];
// Validate group for processing
bool process_valid = false;
if (p_physics) {
if (!pg->physics_nodes.is_empty()) {
process_valid = true;
} else if (pg->owner != nullptr && pg->owner->data.process_thread_messages.has_flag(Node::FLAG_PROCESS_THREAD_MESSAGES_PHYSICS) && pg->call_queue.has_messages()) {
process_valid = true;
}
} else {
if (!pg->nodes.is_empty()) {
process_valid = true;
} else if (pg->owner != nullptr && pg->owner->data.process_thread_messages.has_flag(Node::FLAG_PROCESS_THREAD_MESSAGES) && pg->call_queue.has_messages()) {
process_valid = true;
}
}
if (process_valid) {
pg->last_pass = process_last_pass; // Enable for processing
process_count++;
}
}
nodes_removed_on_group_call_lock--;
if (nodes_removed_on_group_call_lock == 0) {
nodes_removed_on_group_call.clear();
}
}
bool SceneTree::ProcessGroupSort::operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const {
int left_order = p_left->owner ? p_left->owner->data.process_thread_group_order : 0;
int right_order = p_right->owner ? p_right->owner->data.process_thread_group_order : 0;
if (left_order == right_order) {
int left_threaded = p_left->owner != nullptr && p_left->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1;
int right_threaded = p_right->owner != nullptr && p_right->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1;
return left_threaded < right_threaded;
} else {
return left_order < right_order;
}
}
void SceneTree::_remove_process_group(Node *p_node) {
_THREAD_SAFE_METHOD_
ProcessGroup *pg = (ProcessGroup *)p_node->data.process_group;
ERR_FAIL_COND(!pg);
ERR_FAIL_COND(pg->removed);
pg->removed = true;
pg->owner = nullptr;
p_node->data.process_group = nullptr;
process_groups_dirty = true;
}
void SceneTree::_add_process_group(Node *p_node) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!p_node);
ProcessGroup *pg = memnew(ProcessGroup);
pg->owner = p_node;
p_node->data.process_group = pg;
process_groups.push_back(pg);
process_groups_dirty = true;
}
void SceneTree::_remove_node_from_process_group(Node *p_node, Node *p_owner) {
_THREAD_SAFE_METHOD_
ProcessGroup *pg = p_owner ? (ProcessGroup *)p_owner->data.process_group : &default_process_group;
if (p_node->is_processing() || p_node->is_processing_internal()) {
bool found = pg->nodes.erase(p_node);
ERR_FAIL_COND(!found);
}
if (p_node->is_physics_processing() || p_node->is_physics_processing_internal()) {
bool found = pg->physics_nodes.erase(p_node);
ERR_FAIL_COND(!found);
}
}
void SceneTree::_add_node_to_process_group(Node *p_node, Node *p_owner) {
_THREAD_SAFE_METHOD_
ProcessGroup *pg = p_owner ? (ProcessGroup *)p_owner->data.process_group : &default_process_group;
if (p_node->is_processing() || p_node->is_processing_internal()) {
pg->nodes.push_back(p_node);
pg->node_order_dirty = true;
}
if (p_node->is_physics_processing() || p_node->is_physics_processing_internal()) {
pg->physics_nodes.push_back(p_node);
pg->physics_node_order_dirty = true;
}
}
void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) {
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
Vector<Node *> nodes_copy;
{
_THREAD_SAFE_METHOD_
_update_group_order(g);
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
}
Group &g = E->value;
if (g.nodes.is_empty()) {
return;
}
//copy, so copy on write happens in case something is removed from process while being called
//performance is not lost because only if something is added/removed the vector is copied.
Vector<Node *> nodes_copy = g.nodes;
_update_group_order(g);
//copy, so copy on write happens in case something is removed from process while being called
//performance is not lost because only if something is added/removed the vector is copied.
nodes_copy = g.nodes;
}
int gr_node_count = nodes_copy.size();
Node **gr_nodes = nodes_copy.ptrw();
call_lock++;
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock++;
}
Vector<ObjectID> no_context_node_ids; // Nodes may be deleted due to this shortcut input.
@ -909,7 +1171,7 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal
}
Node *n = gr_nodes[i];
if (call_lock && call_skip.has(n)) {
if (nodes_removed_on_group_call.has(n)) {
continue;
}
@ -956,9 +1218,12 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal
}
}
call_lock--;
if (call_lock == 0) {
call_skip.clear();
{
_THREAD_SAFE_METHOD_
nodes_removed_on_group_call_lock--;
if (nodes_removed_on_group_call_lock == 0) {
nodes_removed_on_group_call.clear();
}
}
}
@ -995,6 +1260,7 @@ int64_t SceneTree::get_frame() const {
}
TypedArray<Node> SceneTree::_get_nodes_in_group(const StringName &p_group) {
_THREAD_SAFE_METHOD_
TypedArray<Node> ret;
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
@ -1018,10 +1284,12 @@ TypedArray<Node> SceneTree::_get_nodes_in_group(const StringName &p_group) {
}
bool SceneTree::has_group(const StringName &p_identifier) const {
_THREAD_SAFE_METHOD_
return group_map.has(p_identifier);
}
Node *SceneTree::get_first_node_in_group(const StringName &p_group) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return nullptr; // No group.
@ -1037,6 +1305,7 @@ Node *SceneTree::get_first_node_in_group(const StringName &p_group) {
}
void SceneTree::get_nodes_in_group(const StringName &p_group, List<Node *> *p_list) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
if (!E) {
return;
@ -1073,7 +1342,7 @@ void SceneTree::queue_delete(Object *p_object) {
}
int SceneTree::get_node_count() const {
return node_count;
return nodes_in_tree_count;
}
void SceneTree::set_edited_scene_root(Node *p_node) {
@ -1091,6 +1360,7 @@ Node *SceneTree::get_edited_scene_root() const {
}
void SceneTree::set_current_scene(Node *p_scene) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread.");
ERR_FAIL_COND(p_scene && p_scene->get_parent() != root);
current_scene = p_scene;
}
@ -1100,6 +1370,7 @@ Node *SceneTree::get_current_scene() const {
}
void SceneTree::_change_scene(Node *p_to) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread.");
if (current_scene) {
memdelete(current_scene);
current_scene = nullptr;
@ -1121,6 +1392,7 @@ void SceneTree::_change_scene(Node *p_to) {
}
Error SceneTree::change_scene_to_file(const String &p_path) {
ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Changing scene can only be done from the main thread.");
Ref<PackedScene> new_scene = ResourceLoader::load(p_path);
if (new_scene.is_null()) {
return ERR_CANT_OPEN;
@ -1140,12 +1412,14 @@ Error SceneTree::change_scene_to_packed(const Ref<PackedScene> &p_scene) {
}
Error SceneTree::reload_current_scene() {
ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Reloading scene can only be done from the main thread.");
ERR_FAIL_COND_V(!current_scene, ERR_UNCONFIGURED);
String fname = current_scene->get_scene_file_path();
return change_scene_to_file(fname);
}
void SceneTree::unload_current_scene() {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Unloading the current scene can only be done from the main thread.");
if (current_scene) {
memdelete(current_scene);
current_scene = nullptr;
@ -1153,11 +1427,13 @@ void SceneTree::unload_current_scene() {
}
void SceneTree::add_current_scene(Node *p_current) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Adding a current scene can only be done from the main thread.");
current_scene = p_current;
root->add_child(p_current);
}
Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_always, bool p_process_in_physics, bool p_ignore_time_scale) {
_THREAD_SAFE_METHOD_
Ref<SceneTreeTimer> stt;
stt.instantiate();
stt->set_process_always(p_process_always);
@ -1169,12 +1445,14 @@ Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_a
}
Ref<Tween> SceneTree::create_tween() {
_THREAD_SAFE_METHOD_
Ref<Tween> tween = memnew(Tween(true));
tweens.push_back(tween);
return tween;
}
TypedArray<Tween> SceneTree::get_processed_tweens() {
_THREAD_SAFE_METHOD_
TypedArray<Tween> ret;
ret.resize(tweens.size());
@ -1188,6 +1466,7 @@ TypedArray<Tween> SceneTree::get_processed_tweens() {
}
Ref<MultiplayerAPI> SceneTree::get_multiplayer(const NodePath &p_for_path) const {
ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), Ref<MultiplayerAPI>(), "Multiplayer can only be manipulated from the main thread.");
Ref<MultiplayerAPI> out = multiplayer;
for (const KeyValue<NodePath, Ref<MultiplayerAPI>> &E : custom_multiplayers) {
const Vector<StringName> snames = E.key.get_names();
@ -1213,6 +1492,7 @@ Ref<MultiplayerAPI> SceneTree::get_multiplayer(const NodePath &p_for_path) const
}
void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePath &p_root_path) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Multiplayer can only be manipulated from the main thread.");
if (p_root_path.is_empty()) {
ERR_FAIL_COND(!p_multiplayer.is_valid());
if (multiplayer.is_valid()) {
@ -1232,6 +1512,7 @@ void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePat
}
void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Multiplayer can only be manipulated from the main thread.");
multiplayer_poll = p_enabled;
}
@ -1385,6 +1666,10 @@ void SceneTree::get_argument_options(const StringName &p_function, int p_idx, Li
}
}
void SceneTree::set_disable_node_threading(bool p_disable) {
node_threading_disabled = p_disable;
}
SceneTree::SceneTree() {
if (singleton == nullptr) {
singleton = this;
@ -1397,6 +1682,7 @@ SceneTree::SceneTree() {
GLOBAL_DEF("debug/shapes/collision/draw_2d_outlines", true);
process_group_call_queue_allocator = memnew(CallQueue::Allocator(64));
Math::randomize();
// Create with mainloop.
@ -1534,6 +1820,8 @@ SceneTree::SceneTree() {
#ifdef TOOLS_ENABLED
edited_scene_root = nullptr;
#endif
process_groups.push_back(&default_process_group);
}
SceneTree::~SceneTree() {
@ -1543,6 +1831,15 @@ SceneTree::~SceneTree() {
memdelete(root);
}
// Process groups are not deleted immediately, they may remain around. Delete them now.
for (uint32_t i = 0; i < process_groups.size(); i++) {
if (process_groups[i] != &default_process_group) {
memdelete(process_groups[i]);
}
}
memdelete(process_group_call_queue_allocator);
if (singleton == this) {
singleton = nullptr;
}

View file

@ -33,6 +33,7 @@
#include "core/os/main_loop.h"
#include "core/os/thread_safe.h"
#include "core/templates/paged_allocator.h"
#include "core/templates/self_list.h"
#include "scene/resources/mesh.h"
@ -86,6 +87,34 @@ public:
typedef void (*IdleCallback)();
private:
CallQueue::Allocator *process_group_call_queue_allocator = nullptr;
struct ProcessGroup {
CallQueue call_queue;
Vector<Node *> nodes;
Vector<Node *> physics_nodes;
bool node_order_dirty = true;
bool physics_node_order_dirty = true;
bool removed = false;
Node *owner = nullptr;
uint64_t last_pass = 0;
};
struct ProcessGroupSort {
_FORCE_INLINE_ bool operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const;
};
PagedAllocator<ProcessGroup, true> group_allocator; // Allocate groups on pages, to enhance cache usage.
LocalVector<ProcessGroup *> process_groups;
bool process_groups_dirty = true;
LocalVector<ProcessGroup *> local_process_group_cache; // Used when processing to group what needs to
uint64_t process_last_pass = 1;
ProcessGroup default_process_group;
bool node_threading_disabled = false;
struct Group {
Vector<Node *> nodes;
bool changed = false;
@ -117,7 +146,7 @@ private:
StringName node_renamed_name = "node_renamed";
int64_t current_frame = 0;
int node_count = 0;
int nodes_in_tree_count = 0;
#ifdef TOOLS_ENABLED
Node *edited_scene_root = nullptr;
@ -134,8 +163,10 @@ private:
};
// Safety for when a node is deleted while a group is being called.
int call_lock = 0;
HashSet<Node *> call_skip; // Skip erased nodes.
bool processing = false;
int nodes_removed_on_group_call_lock = 0;
HashSet<Node *> nodes_removed_on_group_call; // Skip erased nodes.
List<ObjectID> delete_queue;
@ -143,7 +174,7 @@ private:
bool ugc_locked = false;
void _flush_ugc();
_FORCE_INLINE_ void _update_group_order(Group &g, bool p_use_priority = false);
_FORCE_INLINE_ void _update_group_order(Group &g);
TypedArray<Node> _get_nodes_in_group(const StringName &p_group);
@ -187,7 +218,15 @@ private:
void remove_from_group(const StringName &p_group, Node *p_node);
void make_group_changed(const StringName &p_group);
void _notify_group_pause(const StringName &p_group, int p_notification);
void _process_group(ProcessGroup *p_group, bool p_physics);
void _process_groups_thread(uint32_t p_index, bool p_physics);
void _process(bool p_physics);
void _remove_process_group(Node *p_node);
void _add_process_group(Node *p_node);
void _remove_node_from_process_group(Node *p_node, Node *p_owner);
void _add_node_to_process_group(Node *p_node, Node *p_owner);
void _call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
void _call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
@ -383,6 +422,7 @@ public:
static void add_idle_callback(IdleCallback p_callback);
void set_disable_node_threading(bool p_disable);
//default texture settings
SceneTree();