From 98c655ec8db17e50afa58284b1dcad754034db4b Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Mon, 10 Apr 2023 18:45:53 +0200 Subject: [PATCH] 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. --- core/object/message_queue.cpp | 11 + core/object/message_queue.h | 15 +- core/object/object.cpp | 5 + core/object/object.h | 2 + core/os/thread.h | 2 + core/templates/hash_map.h | 4 +- core/templates/hash_set.h | 4 +- core/templates/local_vector.h | 4 +- core/templates/vector.h | 5 +- doc/classes/Node.xml | 74 +++++ doc/classes/Object.xml | 6 + editor/editor_node.cpp | 2 + main/main.cpp | 8 + scene/3d/skeleton_3d.cpp | 9 +- scene/main/node.cpp | 472 ++++++++++++++++++++++++++--- scene/main/node.h | 113 ++++++- scene/main/scene_tree.cpp | 555 ++++++++++++++++++++++++++-------- scene/main/scene_tree.h | 50 ++- 18 files changed, 1155 insertions(+), 186 deletions(-) diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index cfd8904af605..05f4e2a8a62c 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -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; } diff --git a/core/object/message_queue.h b/core/object/message_queue.h index 2349c6d8691d..fe261f840ed7 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -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 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 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; diff --git a/core/object/object.cpp b/core/object/object.cpp index 8ec385c0eb07..1d63809fb133 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -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); diff --git a/core/object/object.h b/core/object/object.h index ed2c6254174c..636e4d56fefc 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -923,6 +923,8 @@ public: _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } + void cancel_free(); + Object(); virtual ~Object(); }; diff --git a/core/os/thread.h b/core/os/thread.h index 19e1376ca844..a769bb1df427 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -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()); diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h index 0b846dfaba74..4da73f1cfb76 100644 --- a/core/templates/hash_map.h +++ b/core/templates/hash_map.h @@ -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]; diff --git a/core/templates/hash_set.h b/core/templates/hash_set.h index 97f1b460aa32..00f4acbc9c84 100644 --- a/core/templates/hash_set.h +++ b/core/templates/hash_set.h @@ -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]; diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 22db3a574430..b454821a8f79 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -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() { diff --git a/core/templates/vector.h b/core/templates/vector.h index ae58eb8b161f..d8bac0870f5b 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -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(); } diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 1615c928b5f9..3f79822ca98c 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -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. + + + + + 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. + + + + + + + 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. + + @@ -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]). + + + + + Similar to [method call_deferred_thread_group], but for notifications. + + + + + + + Similar to [method call_thread_safe], but for notifications. + + @@ -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]. + + + + + + Similar to [method call_deferred_thread_group], but for setting properties. + + @@ -785,6 +821,14 @@ Sets whether this is an instance load placeholder. See [InstancePlaceholder]. + + + + + + Similar to [method call_thread_safe], but for setting properties. + + @@ -811,9 +855,24 @@ 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). + + Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process] or the internal version. + 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. + + 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. + + + 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. + + + 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. + 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. @@ -1033,6 +1092,21 @@ Never process. Completely disables processing, ignoring the [SceneTree]'s paused property. This is the inverse of [constant PROCESS_MODE_ALWAYS]. + + 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. + + + Process this node (and children nodes set to inherit) on the main thread. See [member process_thread_group] for more information. + + + Process this node (and children nodes set to inherit) on a sub-thread. See [member process_thread_group] for more information. + + + + + + + Duplicate the node's signals. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 619276b1549d..0a52df1e49f6 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -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]. + + + + 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. + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 864d45230a3b..50416fbadb3e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -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(); diff --git a/main/main.cpp b/main/main.cpp index 707ef6ec3d00..4f99a93bd258 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -178,6 +178,7 @@ static int converter_max_line_length = 100000; HashMap> 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 Remote debug (://[:], 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)) { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 1b4687907909..61e61ead5e35 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -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; } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 1d7a100f7cf5..3aaafeae3085 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -47,10 +47,14 @@ #include 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 &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 &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 p_flags) { + ERR_THREAD_GUARD + if (data.process_thread_group_order == p_flags) { + return; + } + + data.process_thread_messages = p_flags; +} + +BitField 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::get_children(bool p_include_internal) const { + ERR_THREAD_GUARD_V(TypedArray()); TypedArray 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::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const { + ERR_THREAD_GUARD_V(TypedArray()); TypedArray ret; ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret); _update_children_cache(); @@ -1464,6 +1697,7 @@ TypedArray 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::Iterator E = data.grouped.find(p_identifier); if (!E) { @@ -1869,6 +2110,7 @@ TypedArray Node::_get_groups() const { } void Node::get_groups(List *p_groups) const { + ERR_THREAD_GUARD for (const KeyValue &E : data.grouped) { GroupInfo gi; gi.name = E.key; @@ -1878,6 +2120,7 @@ void Node::get_groups(List *p_groups) const { } int Node::get_persistent_group_count() const { + ERR_THREAD_GUARD_V(0); int count = 0; for (const KeyValue &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 Node::create_tween() { + ERR_THREAD_GUARD_V(Ref()); ERR_FAIL_COND_V_MSG(!data.tree, nullptr, "Can't create Tween when not inside scene tree."); Ref tween = get_tree()->create_tween(); tween->bind_node(this); @@ -1994,6 +2240,7 @@ Ref 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 &r_storable_properties) const { + ERR_THREAD_GUARD List pi; get_property_list(&pi); for (List::Element *E = pi.front(); E; E = E->next()) { @@ -2106,6 +2358,7 @@ void Node::get_storable_properties(HashSet &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 &p_state) { + ERR_THREAD_GUARD data.instance_state = p_state; } @@ -2126,6 +2380,7 @@ Ref Node::get_scene_instance_state() const { } void Node::set_scene_inherited_state(const Ref &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 *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 *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 *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 &r_res, Vector &r_leftover_subpath, bool p_last_is_property) const { + ERR_THREAD_GUARD_V(nullptr); Node *node = get_node(p_path); r_res = Ref(); r_leftover_subpath = Vector(); @@ -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 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 &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 &p_event) { } @@ -2846,6 +3116,94 @@ void Node::unhandled_input(const Ref &p_event) { void Node::unhandled_key_input(const Ref &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"); diff --git a/scene/main/node.h b/scene/main/node.h index 7cccf8e702e3..0b242eaf976d 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -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 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 &p_event); void _call_unhandled_key_input(const Ref &p_event); + void _validate_property(PropertyInfo &p_property) const; + protected: virtual void input(const Ref &p_event); virtual void shortcut_input(const Ref &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 p_flags); + BitField 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 &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 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 + 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 + 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.")); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index b6b694ff5508..2b36b750a58b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -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 #include @@ -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::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::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::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 *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_sort; - node_sort.sort(gr_nodes, gr_node_count); - } else { - SortArray node_sort; - node_sort.sort(gr_nodes, gr_node_count); - } + SortArray 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::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } + Vector 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::Iterator E = group_map.find(p_group); + if (!E) { + return; + } + Group &g = E->value; + if (g.nodes.is_empty()) { return; } - Vector 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 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 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::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; + Vector nodes_copy; + { + _THREAD_SAFE_METHOD_ + HashMap::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 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::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } + Vector nodes_copy; + { + _THREAD_SAFE_METHOD_ - _update_group_order(g); + HashMap::Iterator E = group_map.find(p_group); + if (!E) { + return; + } + Group &g = E->value; + if (g.nodes.is_empty()) { + return; + } - Vector 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>::Element *L = timers.back(); //last element for (List>::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>::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 SceneTree::get_debug_paths_material() { + _THREAD_SAFE_METHOD_ + if (debug_paths_material.is_valid()) { return debug_paths_material; } @@ -747,6 +791,8 @@ Ref SceneTree::get_debug_paths_material() { } Ref SceneTree::get_debug_collision_material() { + _THREAD_SAFE_METHOD_ + if (collision_material.is_valid()) { return collision_material; } @@ -764,6 +810,8 @@ Ref SceneTree::get_debug_collision_material() { } Ref SceneTree::get_debug_contact_mesh() { + _THREAD_SAFE_METHOD_ + if (debug_contact_mesh.is_valid()) { return debug_contact_mesh; } @@ -821,6 +869,8 @@ Ref 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::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 &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 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_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 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(); + } + + 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 &p_input, Viewport *p_viewport) { - HashMap::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } + Vector nodes_copy; + { + _THREAD_SAFE_METHOD_ - _update_group_order(g); + HashMap::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 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 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 SceneTree::_get_nodes_in_group(const StringName &p_group) { + _THREAD_SAFE_METHOD_ TypedArray ret; HashMap::Iterator E = group_map.find(p_group); if (!E) { @@ -1018,10 +1284,12 @@ TypedArray 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::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 *p_list) { + _THREAD_SAFE_METHOD_ HashMap::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 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 &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 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 stt; stt.instantiate(); stt->set_process_always(p_process_always); @@ -1169,12 +1445,14 @@ Ref SceneTree::create_timer(double p_delay_sec, bool p_process_a } Ref SceneTree::create_tween() { + _THREAD_SAFE_METHOD_ Ref tween = memnew(Tween(true)); tweens.push_back(tween); return tween; } TypedArray SceneTree::get_processed_tweens() { + _THREAD_SAFE_METHOD_ TypedArray ret; ret.resize(tweens.size()); @@ -1188,6 +1466,7 @@ TypedArray SceneTree::get_processed_tweens() { } Ref SceneTree::get_multiplayer(const NodePath &p_for_path) const { + ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), Ref(), "Multiplayer can only be manipulated from the main thread."); Ref out = multiplayer; for (const KeyValue> &E : custom_multiplayers) { const Vector snames = E.key.get_names(); @@ -1213,6 +1492,7 @@ Ref SceneTree::get_multiplayer(const NodePath &p_for_path) const } void SceneTree::set_multiplayer(Ref 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 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; } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index fc185b4f3776..2124db867efa 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -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 nodes; + Vector 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 group_allocator; // Allocate groups on pages, to enhance cache usage. + + LocalVector process_groups; + bool process_groups_dirty = true; + LocalVector 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 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 call_skip; // Skip erased nodes. + + bool processing = false; + int nodes_removed_on_group_call_lock = 0; + HashSet nodes_removed_on_group_call; // Skip erased nodes. List 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 _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();