From 6d2dbb366b721e008e15685afb92183d197eabc2 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Mon, 10 Apr 2023 18:45:53 +0200 Subject: [PATCH] Refactor Node Processing (WIP) * 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. --- core/object/message_queue.cpp | 11 + core/object/message_queue.h | 14 +- core/object/object.cpp | 5 + core/object/object.h | 2 + core/os/thread.cpp | 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 +- editor/editor_node.cpp | 2 + main/main.cpp | 8 + scene/3d/skeleton_3d.cpp | 9 +- scene/main/node.cpp | 401 ++++++++++++++++++++++++-- scene/main/node.h | 97 ++++++- scene/main/scene_tree.cpp | 529 ++++++++++++++++++++++++++-------- scene/main/scene_tree.h | 48 ++- 17 files changed, 981 insertions(+), 166 deletions(-) diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 83e0c4aea169fe..e195565e72c028 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -432,6 +432,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 68969dfd392623..dd495f7bb7dea1 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -45,6 +45,13 @@ class CallQueue { 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. + typedef PagedAllocator Allocator; + private: enum { TYPE_CALL, @@ -56,12 +63,7 @@ class CallQueue { 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 +142,8 @@ class CallQueue { 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 39cae7c5bd1d57..d1855b9092d71a 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -200,6 +200,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(); @@ -1551,6 +1555,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 4226b5e67b9e6d..b646a65a80a8ba 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -924,6 +924,8 @@ class Object { _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } + void cancel_free(); + Object(); virtual ~Object(); }; diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 92865576f3ccb2..2b60b4ed0bfda1 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -51,7 +51,7 @@ void Thread::_set_platform_functions(const PlatformFunctions &p_functions) { } void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) { - Thread::caller_id = p_caller_id; + Thread::caller_id = _thread_id_hash(std::this_thread::get_id()); if (platform_functions.set_priority) { platform_functions.set_priority(p_settings.priority); } diff --git a/core/os/thread.h b/core/os/thread.h index 6eb21fba65be59..34b9759d2e4258 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -95,6 +95,8 @@ class Thread { // get the ID of the main thread _FORCE_INLINE_ static ID get_main_id() { return main_thread_id; } + _FORCE_INLINE_ static bool is_main_thread() { return main_thread_id == caller_id; } + 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 f3944fcd0df981..145b29eb6b4012 100644 --- a/core/templates/hash_map.h +++ b/core/templates/hash_map.h @@ -97,7 +97,7 @@ class HashMap { } 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 @@ class HashMap { } 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 97f1b460aa328c..00f4acbc9c840b 100644 --- a/core/templates/hash_set.h +++ b/core/templates/hash_set.h @@ -80,7 +80,7 @@ class HashSet { } 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 @@ class HashSet { } 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 5311a94987f3b0..b295b5eef8163c 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -99,11 +99,13 @@ class LocalVector { } } - 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 ae58eb8b161fae..d8bac0870f5b2e 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -71,12 +71,15 @@ class Vector { 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/editor/editor_node.cpp b/editor/editor_node.cpp index f2f12857999d52..7413e88dc4db17 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -633,6 +633,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 bc309219f48a1d..09fe4cc80dc75f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -180,6 +180,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"); @@ -1104,6 +1106,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; @@ -2775,6 +2779,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 1b4687907909bd..61e61ead5e35a5 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 20827366539e1e..0eda3cf67882ca 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())); } @@ -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,53 +850,201 @@ 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_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; + } + + 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) { - data.process_priority = 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_any_processing()) { + _remove_from_process_thread_group(); + data.process_priority = p_priority; + _add_to_process_thread_group(); + } +} + +int Node::get_process_priority() const { + return data.process_priority; +} - // Make sure we are in SceneTree. - if (data.tree == nullptr) { +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; +} - if (is_processing()) { - data.tree->make_group_changed(SNAME("_process")); +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_processing_internal()) { - data.tree->make_group_changed(SNAME("_process_internal")); + if (!is_inside_tree()) { + data.process_thread_group = p_mode; + return; } - if (is_physics_processing()) { - data.tree->make_group_changed(SNAME("_physics_process")); + // Mode changed, must update everything. + _remove_tree_from_process_thread_group(); + if (data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) { + _remove_process_group(); } - if (is_physics_processing_internal()) { - data.tree->make_group_changed(SNAME("_physics_process_internal")); + 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(); } -int Node::get_process_priority() const { - return data.process_priority; +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; } @@ -2787,6 +3050,7 @@ bool Node::is_displayed_folded() const { } void Node::request_ready() { + ERR_THREAD_GUARD data.ready_first = true; } @@ -2822,6 +3086,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) { } @@ -2834,6 +3104,45 @@ 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(); +} + +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::_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); @@ -2885,6 +3194,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); @@ -2898,6 +3209,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); @@ -2964,6 +3284,16 @@ 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); + BIND_CONSTANT(NOTIFICATION_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_EXIT_TREE); BIND_CONSTANT(NOTIFICATION_MOVED_IN_PARENT); @@ -3016,6 +3346,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); @@ -3043,6 +3381,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 b355c27b04a6fe..4e9084da7c9583 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -54,6 +54,18 @@ class Node : public Object { 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 @@ class Node : public Object { 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 @@ class Node : public Object { } }; + 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 @@ class Node : public Object { 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 @@ class Node : public Object { 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,18 @@ class Node : public Object { 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); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -248,6 +284,8 @@ class Node : public Object { 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 +494,12 @@ class Node : public Object { 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 +512,23 @@ class Node : public Object { 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; @@ -500,6 +561,9 @@ class Node : public Object { bool can_process_notification(int p_what) const; bool is_enabled() const; + void set_process_thread_group(ProcessThreadGroup p_mode); + ProcessThreadGroup get_process_thread_group() const; + void request_ready(); static void print_orphan_nodes(); @@ -553,6 +617,19 @@ class Node : public Object { 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); + Node(); ~Node(); }; @@ -579,6 +656,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 b6b694ff550816..9e4988d350415e 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); - _update_group_order(g); + nodes_copy = g.nodes; + } - 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; + } + + _update_group_order(g); - Vector nodes_copy = g.nodes; + 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; + if (node_order_dirty) { + nodes.sort_custom(); + node_order_dirty = false; + } - int gr_node_count = nodes_copy.size(); - Node **gr_nodes = nodes_copy.ptrw(); + // Make a copy, so if nodes are added/removed from process, this does not break + Vector nodes_copy = nodes; - call_lock++; + uint32_t node_count = nodes_copy.size(); + Node **nodes_ptr = (Node **)nodes_copy.ptr(); // Force cast, pointer will not change. - for (int i = 0; i < gr_node_count; i++) { - Node *n = gr_nodes[i]; - if (call_lock && call_skip.has(n)) { + 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()) { + if (!n->can_process() || !n->is_inside_tree()) { continue; } - if (!n->can_process_notification(p_notification)) { + + 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; } - n->notification(p_notification); - //ERR_FAIL_COND(gr_node_count != g.nodes.size()); + 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++; + } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); } } -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; +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; } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; +} + +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; } - _update_group_order(g); + 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) { + 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; + } - //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; @@ -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 fc185b4f37763e..bfae633df5f429 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 @@ class SceneTree : public MainLoop { 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; @@ -134,8 +163,10 @@ class SceneTree : public MainLoop { }; // 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 @@ class SceneTree : public MainLoop { 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 @@ class SceneTree : public MainLoop { 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 @@ class SceneTree : public MainLoop { static void add_idle_callback(IdleCallback p_callback); + void set_disable_node_threading(bool p_disable); //default texture settings SceneTree();