Implemented a new ProcessGroup Node. It can be used to multithread scenes updates.

It was insipred by godot 4's ProcessGroup system, however while Godot 4's implementation tries to hide threaded processing as much as possible, this implementation focuses on making it explicitly known and obvious to the user, in a (hopefully) almost bolierplate free way.

Also with the available options this node can be used for other purposes, like multi threaded cron job like method calls when paired with a Timer in manual mode.
This commit is contained in:
Relintai 2023-06-13 15:08:35 +02:00
parent d4175f9676
commit decade59c8
8 changed files with 1163 additions and 1 deletions

View File

@ -35,6 +35,7 @@
#include "core/object/message_queue.h" #include "core/object/message_queue.h"
#include "core/string/print_string.h" #include "core/string/print_string.h"
#include "instance_placeholder.h" #include "instance_placeholder.h"
#include "process_group.h"
#include "scene/animation/scene_tree_tween.h" #include "scene/animation/scene_tree_tween.h"
#include "scene/resources/packed_scene.h" #include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h" #include "scene/scene_string_names.h"
@ -70,6 +71,21 @@ void Node::_notification(int p_notification) {
get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_physics_process, ptr, 1); get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_physics_process, ptr, 1);
} }
} break;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_PROCESS: {
if (get_script_instance()) {
Variant time = get_process_delta_time();
const Variant *ptr[1] = { &time };
get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_process_group_process, ptr, 1);
}
} break;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS: {
if (get_script_instance()) {
Variant time = get_physics_process_delta_time();
const Variant *ptr[1] = { &time };
get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_process_group_physics_process, ptr, 1);
}
} break; } break;
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
ERR_FAIL_COND(!get_viewport()); ERR_FAIL_COND(!get_viewport());
@ -94,6 +110,24 @@ void Node::_notification(int p_notification) {
_propagate_physics_interpolated(interpolate); _propagate_physics_interpolated(interpolate);
} }
if (get_process_group()) {
if (data.process_group_physics_process) {
get_process_group()->register_node_physics_process(this);
}
if (data.process_group_physics_process_internal) {
get_process_group()->register_node_internal_physics_process(this);
}
if (data.process_group_idle_process) {
get_process_group()->register_node_process(this);
}
if (data.process_group_idle_process_internal) {
get_process_group()->register_node_internal_process(this);
}
}
if (data.input) { if (data.input) {
add_to_group("_vp_input" + itos(get_viewport()->get_instance_id())); add_to_group("_vp_input" + itos(get_viewport()->get_instance_id()));
} }
@ -127,6 +161,24 @@ void Node::_notification(int p_notification) {
data._seen_by.clear(); data._seen_by.clear();
data._sees.clear(); data._sees.clear();
if (get_process_group()) {
if (data.process_group_physics_process) {
get_process_group()->unregister_node_physics_process(this);
}
if (data.process_group_physics_process_internal) {
get_process_group()->unregister_node_internal_physics_process(this);
}
if (data.process_group_idle_process) {
get_process_group()->unregister_node_process(this);
}
if (data.process_group_idle_process_internal) {
get_process_group()->unregister_node_internal_process(this);
}
}
if (data.input) { if (data.input) {
remove_from_group("_vp_input" + itos(get_viewport()->get_instance_id())); remove_from_group("_vp_input" + itos(get_viewport()->get_instance_id()));
} }
@ -171,6 +223,14 @@ void Node::_notification(int p_notification) {
set_physics_process(true); set_physics_process(true);
} }
if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_process_group_process)) {
set_process_group_process(true);
}
if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_process_group_physics_process)) {
set_process_group_physics_process(true);
}
get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready, nullptr, 0); get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready, nullptr, 0);
} }
@ -279,6 +339,11 @@ void Node::_propagate_enter_tree() {
data.world = data.parent->data.world; data.world = data.parent->data.world;
} }
data.process_group = Object::cast_to<ProcessGroup>(this);
if (!data.process_group && data.parent) {
data.process_group = data.parent->data.process_group;
}
data.inside_tree = true; data.inside_tree = true;
for (RBMap<StringName, GroupData>::Element *E = data.grouped.front(); E; E = E->next()) { for (RBMap<StringName, GroupData>::Element *E = data.grouped.front(); E; E = E->next()) {
@ -414,6 +479,7 @@ void Node::_propagate_exit_tree() {
data.viewport = nullptr; data.viewport = nullptr;
data.world = nullptr; data.world = nullptr;
data.process_group = nullptr;
if (data.tree) { if (data.tree) {
data.tree->tree_changed(); data.tree->tree_changed();
@ -1039,6 +1105,14 @@ bool Node::can_process_notification(int p_what) const {
return data.idle_process_internal; return data.idle_process_internal;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: case NOTIFICATION_INTERNAL_PHYSICS_PROCESS:
return data.physics_process_internal; return data.physics_process_internal;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_PROCESS:
return data.process_group_idle_process;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_INTERNAL_PROCESS:
return data.process_group_idle_process_internal;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS:
return data.process_group_physics_process;
case ProcessGroup::NOTIFICATION_PROCESS_GROUP_INTERNAL_PHYSICS_PROCESS:
return data.process_group_physics_process_internal;
} }
return true; return true;
@ -1189,12 +1263,124 @@ void Node::set_process_priority(int p_priority) {
if (is_physics_processing_internal()) { if (is_physics_processing_internal()) {
data.tree->make_group_changed("physics_process_internal"); data.tree->make_group_changed("physics_process_internal");
} }
if (is_process_group_processing()) {
if (get_process_group()) {
get_process_group()->node_process_changed(this);
}
}
if (is_process_group_processing_internal()) {
if (get_process_group()) {
get_process_group()->node_internal_process_changed(this);
}
}
if (is_process_group_physics_processing()) {
if (get_process_group()) {
get_process_group()->node_physics_process_changed(this);
}
}
if (is_process_group_physics_processing_internal()) {
if (get_process_group()) {
get_process_group()->node_internal_physics_process_changed(this);
}
}
} }
int Node::get_process_priority() const { int Node::get_process_priority() const {
return data.process_priority; return data.process_priority;
} }
void Node::set_process_group_physics_process(bool p_process) {
if (data.process_group_physics_process == p_process) {
return;
}
data.process_group_physics_process = p_process;
if (get_process_group()) {
if (data.process_group_physics_process) {
get_process_group()->register_node_physics_process(this);
} else {
get_process_group()->unregister_node_physics_process(this);
}
}
_change_notify("process_group_physics_process");
}
bool Node::is_process_group_physics_processing() const {
return data.process_group_physics_process;
}
void Node::set_process_group_physics_process_internal(bool p_process_internal) {
if (data.process_group_physics_process_internal == p_process_internal) {
return;
}
data.process_group_physics_process_internal = p_process_internal;
if (get_process_group()) {
if (data.process_group_physics_process_internal) {
get_process_group()->register_node_internal_physics_process(this);
} else {
get_process_group()->unregister_node_internal_physics_process(this);
}
}
_change_notify("process_group_physics_process_internal");
}
bool Node::is_process_group_physics_processing_internal() const {
return data.process_group_physics_process_internal;
}
void Node::set_process_group_process(bool p_idle_process) {
if (data.process_group_idle_process == p_idle_process) {
return;
}
data.process_group_idle_process = p_idle_process;
if (get_process_group()) {
if (data.process_group_idle_process) {
get_process_group()->register_node_process(this);
} else {
get_process_group()->unregister_node_process(this);
}
}
_change_notify("process_group_idle_process");
}
bool Node::is_process_group_processing() const {
return data.process_group_idle_process;
}
void Node::set_process_group_process_internal(bool p_idle_process_internal) {
if (data.process_group_idle_process_internal == p_idle_process_internal) {
return;
}
data.process_group_idle_process_internal = p_idle_process_internal;
if (get_process_group()) {
if (data.process_group_idle_process_internal) {
get_process_group()->register_node_internal_process(this);
} else {
get_process_group()->unregister_node_internal_process(this);
}
}
_change_notify("process_group_idle_process_internal");
}
bool Node::is_process_group_processing_internal() const {
return data.process_group_idle_process_internal;
}
void Node::set_process_input(bool p_enable) { void Node::set_process_input(bool p_enable) {
if (p_enable == data.input) { if (p_enable == data.input) {
return; return;
@ -3282,6 +3468,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_filename"), &Node::get_filename); ClassDB::bind_method(D_METHOD("get_filename"), &Node::get_filename);
ClassDB::bind_method(D_METHOD("propagate_notification", "what"), &Node::propagate_notification); ClassDB::bind_method(D_METHOD("propagate_notification", "what"), &Node::propagate_notification);
ClassDB::bind_method(D_METHOD("propagate_call", "method", "args", "parent_first"), &Node::propagate_call, DEFVAL(Array()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("propagate_call", "method", "args", "parent_first"), &Node::propagate_call, DEFVAL(Array()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_physics_process", "enable"), &Node::set_physics_process); ClassDB::bind_method(D_METHOD("set_physics_process", "enable"), &Node::set_physics_process);
ClassDB::bind_method(D_METHOD("get_physics_process_delta_time"), &Node::get_physics_process_delta_time); ClassDB::bind_method(D_METHOD("get_physics_process_delta_time"), &Node::get_physics_process_delta_time);
ClassDB::bind_method(D_METHOD("is_physics_processing"), &Node::is_physics_processing); ClassDB::bind_method(D_METHOD("is_physics_processing"), &Node::is_physics_processing);
@ -3290,6 +3477,16 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_process_priority", "priority"), &Node::set_process_priority); 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("get_process_priority"), &Node::get_process_priority);
ClassDB::bind_method(D_METHOD("is_processing"), &Node::is_processing); ClassDB::bind_method(D_METHOD("is_processing"), &Node::is_processing);
ClassDB::bind_method(D_METHOD("set_process_group_physics_process", "enable"), &Node::set_process_group_physics_process);
ClassDB::bind_method(D_METHOD("is_process_group_physics_processing"), &Node::is_process_group_physics_processing);
ClassDB::bind_method(D_METHOD("set_process_group_process", "enable"), &Node::set_process_group_process);
ClassDB::bind_method(D_METHOD("is_process_group_processing"), &Node::is_process_group_processing);
ClassDB::bind_method(D_METHOD("set_process_group_physics_process_internal", "enable"), &Node::set_process_group_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_process_group_physics_processing_internal"), &Node::is_process_group_physics_processing_internal);
ClassDB::bind_method(D_METHOD("set_process_group_process_internal", "enable"), &Node::set_process_group_process_internal);
ClassDB::bind_method(D_METHOD("is_process_group_processing_internal"), &Node::is_process_group_processing_internal);
ClassDB::bind_method(D_METHOD("set_process_input", "enable"), &Node::set_process_input); 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); ClassDB::bind_method(D_METHOD("is_processing_input"), &Node::is_processing_input);
ClassDB::bind_method(D_METHOD("set_process_unhandled_input", "enable"), &Node::set_process_unhandled_input); ClassDB::bind_method(D_METHOD("set_process_unhandled_input", "enable"), &Node::set_process_unhandled_input);
@ -3330,6 +3527,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport); ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport);
ClassDB::bind_method(D_METHOD("get_world"), &Node::get_world); ClassDB::bind_method(D_METHOD("get_world"), &Node::get_world);
ClassDB::bind_method(D_METHOD("get_process_group"), &Node::get_process_group);
ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_delete); ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_delete);
@ -3478,6 +3676,8 @@ void Node::_bind_methods() {
BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_process_group_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_process_group_physics_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_enter_tree")); BIND_VMETHOD(MethodInfo("_enter_tree"));
BIND_VMETHOD(MethodInfo("_exit_tree")); BIND_VMETHOD(MethodInfo("_exit_tree"));
BIND_VMETHOD(MethodInfo("_ready")); BIND_VMETHOD(MethodInfo("_ready"));
@ -3512,6 +3712,10 @@ Node::Node() {
data.process_priority = 0; data.process_priority = 0;
data.physics_process_internal = false; data.physics_process_internal = false;
data.idle_process_internal = false; data.idle_process_internal = false;
data.process_group_physics_process = false;
data.process_group_idle_process = false;
data.process_group_physics_process_internal = false;
data.process_group_idle_process_internal = false;
data.inside_tree = false; data.inside_tree = false;
data.ready_notified = false; data.ready_notified = false;
data.physics_interpolated = true; data.physics_interpolated = true;
@ -3533,6 +3737,7 @@ Node::Node() {
data.in_constructor = true; data.in_constructor = true;
data.viewport = nullptr; data.viewport = nullptr;
data.world = nullptr; data.world = nullptr;
data.process_group = nullptr;
data.use_placeholder = false; data.use_placeholder = false;
data.display_folded = false; data.display_folded = false;
data.ready_first = true; data.ready_first = true;

View File

@ -41,6 +41,7 @@ class Viewport;
class World; class World;
class SceneState; class SceneState;
class SceneTreeTween; class SceneTreeTween;
class ProcessGroup;
class Node : public Object { class Node : public Object {
GDCLASS(Node, Object); GDCLASS(Node, Object);
@ -122,6 +123,7 @@ private:
Viewport *viewport; Viewport *viewport;
World *world; World *world;
ProcessGroup *process_group;
RBMap<StringName, GroupData> grouped; RBMap<StringName, GroupData> grouped;
List<Node *>::Element *OW; // owned element List<Node *>::Element *OW; // owned element
@ -154,6 +156,12 @@ private:
bool unhandled_input : 1; bool unhandled_input : 1;
bool unhandled_key_input : 1; bool unhandled_key_input : 1;
bool process_group_physics_process : 1;
bool process_group_idle_process : 1;
bool process_group_physics_process_internal : 1;
bool process_group_idle_process_internal : 1;
// Physics interpolation can be turned on and off on a per node basis. // Physics interpolation can be turned on and off on a per node basis.
// This only takes effect when the SceneTree (or project setting) physics interpolation // This only takes effect when the SceneTree (or project setting) physics interpolation
// is switched on. // is switched on.
@ -458,6 +466,18 @@ public:
void set_process_priority(int p_priority); void set_process_priority(int p_priority);
int get_process_priority() const; int get_process_priority() const;
void set_process_group_physics_process(bool p_process);
bool is_process_group_physics_processing() const;
void set_process_group_process(bool p_idle_process);
bool is_process_group_processing() const;
void set_process_group_physics_process_internal(bool p_process_internal);
bool is_process_group_physics_processing_internal() const;
void set_process_group_process_internal(bool p_idle_process_internal);
bool is_process_group_processing_internal() const;
void set_process_input(bool p_enable); void set_process_input(bool p_enable);
bool is_processing_input() const; bool is_processing_input() const;
@ -468,9 +488,17 @@ public:
bool is_processing_unhandled_key_input() const; bool is_processing_unhandled_key_input() const;
_FORCE_INLINE_ bool _is_any_processing() const { _FORCE_INLINE_ bool _is_any_processing() const {
return _is_any_normal_processing() || _is_any_process_group_processing();
}
_FORCE_INLINE_ bool _is_any_normal_processing() const {
return data.idle_process || data.idle_process_internal || data.physics_process || data.physics_process_internal; return data.idle_process || data.idle_process_internal || data.physics_process || data.physics_process_internal;
} }
_FORCE_INLINE_ bool _is_any_process_group_processing() const {
return data.process_group_idle_process || data.process_group_idle_process_internal || data.process_group_physics_process || data.process_group_physics_process_internal;
}
int get_position_in_parent() const; int get_position_in_parent() const;
Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const; Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const;
@ -545,6 +573,10 @@ public:
return data.world; return data.world;
} }
_FORCE_INLINE_ ProcessGroup *get_process_group() const {
return data.process_group;
}
virtual String get_configuration_warning() const; virtual String get_configuration_warning() const;
void update_configuration_warning(); void update_configuration_warning();

View File

@ -0,0 +1,756 @@
#include "process_group.h"
#include "core/config/engine.h"
#include "scene_tree.h"
ProcessGroup::ProcessMode ProcessGroup::get_process_mode() const {
return _process_mode;
}
void ProcessGroup::set_process_mode(const ProcessGroup::ProcessMode value) {
_THREAD_SAFE_METHOD_
if (_process_mode == value) {
return;
}
_unregister_scene_tree_groups();
_process_mode = value;
if (_process_mode == PROCESS_MODE_SCENE_TREE) {
_register_scene_tree_groups();
}
if (_process_mode == PROCESS_MODE_NORMAL) {
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) != 0) {
set_process_internal(true);
} else {
set_process_internal(false);
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) != 0) {
set_physics_process_internal(true);
} else {
set_physics_process_internal(false);
}
} else {
set_process_internal(false);
set_physics_process_internal(false);
}
//manual doesn't need anything
}
ProcessGroup::Mode ProcessGroup::get_mode() const {
return _mode;
}
void ProcessGroup::set_mode(const ProcessGroup::Mode value) {
_THREAD_SAFE_METHOD_
if (_mode == value) {
return;
}
_mode = value;
}
int ProcessGroup::get_group_flags() const {
return _group_flags;
}
void ProcessGroup::set_group_flags(const int value) {
_THREAD_SAFE_METHOD_
if (_group_flags == value) {
return;
}
_unregister_scene_tree_groups();
_group_flags = value;
_register_scene_tree_groups();
if (_process_mode == PROCESS_MODE_NORMAL) {
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) != 0) {
set_process_internal(true);
} else {
set_process_internal(false);
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) != 0) {
set_physics_process_internal(true);
} else {
set_physics_process_internal(false);
}
}
}
bool ProcessGroup::get_use_priority() const {
return _use_priority;
}
void ProcessGroup::set_use_priority(const bool value) {
_use_priority = value;
}
bool ProcessGroup::get_use_threads() const {
return _use_threads;
}
void ProcessGroup::set_use_threads(const bool value) {
_THREAD_SAFE_METHOD_
if (_use_threads == value) {
return;
}
_use_threads = value;
if (!should_use_threads()) {
_quit_thread();
} else {
_setup_thread();
}
}
void ProcessGroup::trigger_process() {
if (_mode == MODE_OFF) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) == 0) {
return;
}
if (should_use_threads()) {
ERR_FAIL_COND(is_working());
switch (_mode) {
case MODE_WAIT: {
_current_process_type.set(CURRENT_PROCESS_TYPE_PROCESS);
_process_semaphore.post();
} break;
case MODE_TRIGGER: {
_current_process_type.set(CURRENT_PROCESS_TYPE_PROCESS);
_process_semaphore.post();
} break;
case MODE_TRIGGER_UNIQUE: {
if (_process_semaphore.get() > 0) {
return;
}
_current_process_type.set(CURRENT_PROCESS_TYPE_PROCESS);
_process_semaphore.post();
} break;
case MODE_TRIGGER_DEFERRED: {
call_deferred("_trigger_process_deferred");
} break;
case MODE_OFF:
default:
break;
}
} else {
switch (_mode) {
case MODE_WAIT:
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE: {
_handle_process();
} break;
case MODE_TRIGGER_DEFERRED: {
call_deferred("_handle_process");
} break;
case MODE_OFF:
default:
break;
}
}
}
void ProcessGroup::wait_process() {
if (_mode == MODE_OFF) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) == 0) {
return;
}
if (should_use_threads()) {
if (!is_working()) {
return;
}
switch (_mode) {
case MODE_WAIT: {
// if the worker thread finished, this will just consume the count from the semaphore
_main_semaphore.wait();
} break;
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE:
case MODE_TRIGGER_DEFERRED:
case MODE_OFF:
default: {
// No waiting needed for these
} break;
}
}
}
void ProcessGroup::trigger_physics_process() {
//No physics in editor
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
if (_mode == MODE_OFF) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) == 0) {
return;
}
if (should_use_threads()) {
ERR_FAIL_COND(is_working());
switch (_mode) {
case MODE_WAIT: {
_current_process_type.set(CURRENT_PROCESS_TYPE_PHYSICS_PROCESS);
_process_semaphore.post();
} break;
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE:
case MODE_TRIGGER_DEFERRED:
case MODE_OFF:
default: {
//Won't work with physics process. Unsafe.
} break;
}
} else {
switch (_mode) {
case MODE_WAIT:
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE: {
_handle_physics_process();
} break;
case MODE_TRIGGER_DEFERRED:
case MODE_OFF:
default: {
//Won't work with physics process. Unsafe.
} break;
}
}
}
void ProcessGroup::wait_physics_process() {
//No physics in editor
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
if (_mode == MODE_OFF) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) == 0) {
return;
}
if (should_use_threads()) {
if (!is_working()) {
return;
}
switch (_mode) {
case MODE_WAIT: {
// if the worker thread finished, this will just consume the count from the semaphore
_main_semaphore.wait();
} break;
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE:
case MODE_TRIGGER_DEFERRED:
case MODE_OFF:
default: {
// No waiting needed for these
} break;
}
}
}
void ProcessGroup::register_node_process(Node *p_node) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_MSG(_process_group.nodes.find(p_node) != -1, "Node already registered for process.");
_process_group.nodes.push_back(p_node);
_process_group.changed = true;
}
void ProcessGroup::unregister_node_process(Node *p_node) {
_THREAD_SAFE_METHOD_
_process_group.nodes.erase(p_node);
}
void ProcessGroup::node_process_changed(Node *p_node) {
_THREAD_SAFE_METHOD_
_process_group.changed = true;
}
void ProcessGroup::register_node_internal_process(Node *p_node) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_MSG(_internal_process_group.nodes.find(p_node) != -1, "Node already registered for process.");
_internal_process_group.nodes.push_back(p_node);
_internal_process_group.changed = true;
}
void ProcessGroup::unregister_node_internal_process(Node *p_node) {
_THREAD_SAFE_METHOD_
_internal_process_group.nodes.erase(p_node);
}
void ProcessGroup::node_internal_process_changed(Node *p_node) {
_THREAD_SAFE_METHOD_
_internal_process_group.changed = true;
}
void ProcessGroup::register_node_physics_process(Node *p_node) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_MSG(_physics_process_group.nodes.find(p_node) != -1, "Node already registered for process.");
_physics_process_group.nodes.push_back(p_node);
_physics_process_group.changed = true;
}
void ProcessGroup::unregister_node_physics_process(Node *p_node) {
_THREAD_SAFE_METHOD_
_physics_process_group.nodes.erase(p_node);
}
void ProcessGroup::node_physics_process_changed(Node *p_node) {
_THREAD_SAFE_METHOD_
_physics_process_group.changed = true;
}
void ProcessGroup::register_node_internal_physics_process(Node *p_node) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_MSG(_internal_physics_process_group.nodes.find(p_node) != -1, "Node already registered for process.");
_internal_physics_process_group.nodes.push_back(p_node);
_internal_physics_process_group.changed = true;
}
void ProcessGroup::unregister_node_internal_physics_process(Node *p_node) {
_THREAD_SAFE_METHOD_
_internal_physics_process_group.nodes.erase(p_node);
}
void ProcessGroup::node_internal_physics_process_changed(Node *p_node) {
_THREAD_SAFE_METHOD_
_internal_physics_process_group.changed = true;
}
bool ProcessGroup::should_use_threads() const {
#if !defined(NO_THREADS)
return _use_threads;
#else
return false;
#endif
}
bool ProcessGroup::is_working() const {
if (_mode == MODE_WAIT) {
return _current_process_type.get() != CURRENT_PROCESS_TYPE_NONE;
}
return false;
}
String ProcessGroup::get_configuration_warning() const {
String w;
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) != 0) {
// _use_threads -> need to specifically check for the property's value here
if (_use_threads) {
switch (_mode) {
case MODE_WAIT: {
//OK
} break;
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE:
case MODE_TRIGGER_DEFERRED: {
w += "In Trigger, Trigger Unique, Trigger Deferred modes physics process group flag cannot be used when threading is on, and it will do nothing as it's only safe to communicate with the physics server at certain times!";
} break;
case MODE_OFF:
default:
break;
}
} else {
switch (_mode) {
case MODE_WAIT:
case MODE_TRIGGER:
case MODE_TRIGGER_UNIQUE: {
// OK
} break;
case MODE_TRIGGER_DEFERRED: {
w += "In Trigger Deferred mode physics process group flag cannot be used when threading is off, and it will do nothing as it's only safe to communicate with the physics server at certain times!";
} break;
case MODE_OFF:
default:
break;
}
}
}
return w;
}
ProcessGroup::ProcessGroup() {
_process_mode = PROCESS_MODE_SCENE_TREE;
_mode = MODE_WAIT;
_group_flags = PROCESS_GROUP_FLAG_PROCESS;
_use_priority = false;
_use_threads = true;
_tread_run = true;
_thread = NULL;
_current_process_type.set(CURRENT_PROCESS_TYPE_NONE);
}
ProcessGroup::~ProcessGroup() {
_cleanup();
}
void ProcessGroup::_setup() {
_current_process_type.set(CURRENT_PROCESS_TYPE_NONE);
_setup_thread();
}
void ProcessGroup::_cleanup() {
_tread_run = false;
_process_semaphore.post();
_cleanup_thread();
}
void ProcessGroup::_setup_thread() {
if (!should_use_threads()) {
return;
}
if (_thread) {
return;
}
_tread_run = true;
_thread = memnew(Thread);
_thread->start(_thread_func, this);
}
void ProcessGroup::_quit_thread() {
if (!_tread_run || !_thread) {
return;
}
_tread_run = false;
_process_semaphore.post();
_cleanup_thread();
}
void ProcessGroup::_quit_thread_deferred() {
if (!_tread_run || !_thread) {
return;
}
_tread_run = false;
_process_semaphore.post();
call_deferred("_cleanup_thread");
}
void ProcessGroup::_cleanup_thread() {
if (_tread_run) {
_tread_run = false;
_process_semaphore.post();
}
if (!_thread) {
return;
}
_thread->wait_to_finish();
memdelete(_thread);
_thread = NULL;
}
void ProcessGroup::_register_scene_tree_groups() {
if (!is_inside_tree()) {
return;
}
if (_process_mode != PROCESS_MODE_SCENE_TREE) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) != 0) {
add_to_group("_pg_process");
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) != 0) {
add_to_group("_pg_physics_process");
}
}
void ProcessGroup::_unregister_scene_tree_groups() {
if (!is_inside_tree()) {
return;
}
if (_process_mode != PROCESS_MODE_SCENE_TREE) {
return;
}
if ((_group_flags & PROCESS_GROUP_FLAG_PROCESS) != 0) {
remove_from_group("_pg_process");
}
if ((_group_flags & PROCESS_GROUP_FLAG_PHYSICS_PROCESS) != 0) {
remove_from_group("_pg_physics_process");
}
}
void ProcessGroup::_trigger_process_deferred() {
if (should_use_threads()) {
_current_process_type.set(CURRENT_PROCESS_TYPE_PROCESS);
_process_semaphore.post();
} else {
_handle_process();
}
}
void ProcessGroup::_handle_process() {
Vector<Node *> nodes_copy;
// Normal Process
{
_THREAD_SAFE_METHOD_
_update_group_order(_process_group);
nodes_copy = _process_group.nodes;
}
Node **nodes = nodes_copy.ptrw();
int node_count = nodes_copy.size();
for (int i = 0; i < node_count; i++) {
Node *n = nodes[i];
if (!n->can_process()) {
continue;
}
if (!n->can_process_notification(NOTIFICATION_PROCESS_GROUP_PROCESS)) {
continue;
}
n->notification(NOTIFICATION_PROCESS_GROUP_PROCESS);
}
// Internal Process
{
_THREAD_SAFE_METHOD_
_update_group_order(_internal_process_group);
nodes_copy = _internal_process_group.nodes;
}
nodes = nodes_copy.ptrw();
node_count = nodes_copy.size();
for (int i = 0; i < node_count; i++) {
Node *n = nodes[i];
if (!n->can_process()) {
continue;
}
if (!n->can_process_notification(NOTIFICATION_PROCESS_GROUP_INTERNAL_PROCESS)) {
continue;
}
n->notification(NOTIFICATION_PROCESS_GROUP_INTERNAL_PROCESS);
}
real_t delta = SceneTree::get_singleton()->get_idle_process_time();
emit_signal("process", delta);
}
void ProcessGroup::_handle_physics_process() {
Vector<Node *> nodes_copy;
// Normal Physics Process
{
_THREAD_SAFE_METHOD_
_update_group_order(_physics_process_group);
nodes_copy = _physics_process_group.nodes;
}
Node **nodes = nodes_copy.ptrw();
int node_count = nodes_copy.size();
for (int i = 0; i < node_count; i++) {
Node *n = nodes[i];
if (!n->can_process()) {
continue;
}
if (!n->can_process_notification(NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS)) {
continue;
}
n->notification(NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS);
}
// Internal Physics Process
{
_THREAD_SAFE_METHOD_
_update_group_order(_internal_physics_process_group);
nodes_copy = _internal_physics_process_group.nodes;
}
nodes = nodes_copy.ptrw();
node_count = nodes_copy.size();
for (int i = 0; i < node_count; i++) {
Node *n = nodes[i];
if (!n->can_process()) {
continue;
}
if (!n->can_process_notification(NOTIFICATION_PROCESS_GROUP_INTERNAL_PHYSICS_PROCESS)) {
continue;
}
n->notification(NOTIFICATION_PROCESS_GROUP_INTERNAL_PHYSICS_PROCESS);
}
real_t delta = SceneTree::get_singleton()->get_physics_process_time();
emit_signal("process", delta);
}
void ProcessGroup::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_setup();
_register_scene_tree_groups();
} break;
case NOTIFICATION_EXIT_TREE: {
_unregister_scene_tree_groups();
_cleanup();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
trigger_process();
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
trigger_physics_process();
} break;
}
}
void ProcessGroup::_update_group_order(Group &g) {
if (!g.changed) {
return;
}
if (g.nodes.empty()) {
return;
}
Node **nodes = g.nodes.ptrw();
int node_count = g.nodes.size();
if (_use_priority) {
SortArray<Node *, Node::ComparatorWithPriority> node_sort;
node_sort.sort(nodes, node_count);
} else {
SortArray<Node *, Node::Comparator> node_sort;
node_sort.sort(nodes, node_count);
}
g.changed = false;
}
void ProcessGroup::_thread_func(void *udata) {
ProcessGroup *self = static_cast<ProcessGroup *>(udata);
while (self->_tread_run) {
CurrentProcessType current_process_type = static_cast<CurrentProcessType>(self->_current_process_type.get());
switch (current_process_type) {
case CURRENT_PROCESS_TYPE_PROCESS: {
self->_handle_process();
} break;
case CURRENT_PROCESS_TYPE_PHYSICS_PROCESS: {
self->_handle_physics_process();
} break;
case CURRENT_PROCESS_TYPE_NONE:
default: {
// do nothing
} break;
}
// Let the main thread continue
if (current_process_type != CURRENT_PROCESS_TYPE_NONE && self->_mode == MODE_WAIT) {
self->_current_process_type.set(CURRENT_PROCESS_TYPE_NONE);
self->_main_semaphore.post();
}
self->_process_semaphore.wait();
}
}
void ProcessGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_process_mode"), &ProcessGroup::get_process_mode);
ClassDB::bind_method(D_METHOD("set_process_mode", "value"), &ProcessGroup::set_process_mode);
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "SceneTree,Normal,Manual"), "set_process_mode", "get_process_mode");
ClassDB::bind_method(D_METHOD("get_mode"), &ProcessGroup::get_mode);
ClassDB::bind_method(D_METHOD("set_mode", "value"), &ProcessGroup::set_mode);
ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Wait,Trigger,Trigger unique,Trigger Deferred,Off"), "set_mode", "get_mode");
ClassDB::bind_method(D_METHOD("get_group_flags"), &ProcessGroup::get_group_flags);
ClassDB::bind_method(D_METHOD("set_group_flags", "value"), &ProcessGroup::set_group_flags);
ADD_PROPERTY(PropertyInfo(Variant::INT, "group_flags", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_group_flags", "get_group_flags");
ClassDB::bind_method(D_METHOD("get_use_priority"), &ProcessGroup::get_use_priority);
ClassDB::bind_method(D_METHOD("set_use_priority", "value"), &ProcessGroup::set_use_priority);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_priority"), "set_use_priority", "get_use_priority");
ClassDB::bind_method(D_METHOD("get_use_threads"), &ProcessGroup::get_use_threads);
ClassDB::bind_method(D_METHOD("set_use_threads", "value"), &ProcessGroup::set_use_threads);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "get_use_threads");
ClassDB::bind_method(D_METHOD("trigger_process"), &ProcessGroup::trigger_process);
ClassDB::bind_method(D_METHOD("wait_process"), &ProcessGroup::wait_process);
ClassDB::bind_method(D_METHOD("trigger_physics_process"), &ProcessGroup::trigger_physics_process);
ClassDB::bind_method(D_METHOD("wait_physics_process"), &ProcessGroup::wait_physics_process);
ClassDB::bind_method(D_METHOD("should_use_threads"), &ProcessGroup::should_use_threads);
ClassDB::bind_method(D_METHOD("is_working"), &ProcessGroup::is_working);
ClassDB::bind_method(D_METHOD("_cleanup_thread"), &ProcessGroup::_cleanup_thread);
ClassDB::bind_method(D_METHOD("_trigger_process_deferred"), &ProcessGroup::_trigger_process_deferred);
ADD_SIGNAL(MethodInfo("process", PropertyInfo(Variant::REAL, "delta")));
ADD_SIGNAL(MethodInfo("physics_process", PropertyInfo(Variant::REAL, "delta")));
BIND_ENUM_CONSTANT(PROCESS_MODE_SCENE_TREE);
BIND_ENUM_CONSTANT(PROCESS_MODE_NORMAL);
BIND_ENUM_CONSTANT(PROCESS_MODE_MANUAL);
BIND_ENUM_CONSTANT(MODE_WAIT);
BIND_ENUM_CONSTANT(MODE_TRIGGER);
BIND_ENUM_CONSTANT(MODE_TRIGGER_UNIQUE);
BIND_ENUM_CONSTANT(MODE_TRIGGER_DEFERRED);
BIND_ENUM_CONSTANT(MODE_OFF);
BIND_ENUM_CONSTANT(PROCESS_GROUP_FLAG_PROCESS);
BIND_ENUM_CONSTANT(PROCESS_GROUP_FLAG_PHYSICS_PROCESS);
BIND_CONSTANT(NOTIFICATION_PROCESS_GROUP_PROCESS);
BIND_CONSTANT(NOTIFICATION_PROCESS_GROUP_INTERNAL_PROCESS);
BIND_CONSTANT(NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS);
BIND_CONSTANT(NOTIFICATION_PROCESS_GROUP_INTERNAL_PHYSICS_PROCESS);
}

149
scene/main/process_group.h Normal file
View File

@ -0,0 +1,149 @@
#ifndef PROCESS_GROUP_H
#define PROCESS_GROUP_H
#include "core/os/safe_refcount.h"
#include "core/os/semaphore.h"
#include "core/os/thread.h"
#include "core/os/thread_safe.h"
#include "scene/main/node.h"
class ProcessGroup : public Node {
GDCLASS(ProcessGroup, Node);
_THREAD_SAFE_CLASS_
public:
enum ProcessMode {
PROCESS_MODE_SCENE_TREE,
PROCESS_MODE_NORMAL,
PROCESS_MODE_MANUAL,
};
enum Mode {
MODE_WAIT,
MODE_TRIGGER,
MODE_TRIGGER_UNIQUE,
MODE_TRIGGER_DEFERRED,
MODE_OFF,
};
enum ProcessGroupFlags {
PROCESS_GROUP_FLAG_PROCESS = 1 << 0,
PROCESS_GROUP_FLAG_PHYSICS_PROCESS = 1 << 1,
};
enum {
NOTIFICATION_PROCESS_GROUP_PROCESS = 60,
NOTIFICATION_PROCESS_GROUP_INTERNAL_PROCESS,
NOTIFICATION_PROCESS_GROUP_PHYSICS_PROCESS,
NOTIFICATION_PROCESS_GROUP_INTERNAL_PHYSICS_PROCESS,
};
public:
ProcessMode get_process_mode() const;
void set_process_mode(const ProcessMode value);
Mode get_mode() const;
void set_mode(const Mode value);
int get_group_flags() const;
void set_group_flags(const int value);
bool get_use_priority() const;
void set_use_priority(const bool value);
bool get_use_threads() const;
void set_use_threads(const bool value);
void trigger_process();
void wait_process();
void trigger_physics_process();
void wait_physics_process();
void register_node_process(Node *p_node);
void unregister_node_process(Node *p_node);
void node_process_changed(Node *p_node);
void register_node_internal_process(Node *p_node);
void unregister_node_internal_process(Node *p_node);
void node_internal_process_changed(Node *p_node);
void register_node_physics_process(Node *p_node);
void unregister_node_physics_process(Node *p_node);
void node_physics_process_changed(Node *p_node);
void register_node_internal_physics_process(Node *p_node);
void unregister_node_internal_physics_process(Node *p_node);
void node_internal_physics_process_changed(Node *p_node);
//This way the property doesn't lose it's vlaue when using a no thread build
bool should_use_threads() const;
bool is_working() const;
String get_configuration_warning() const;
ProcessGroup();
~ProcessGroup();
protected:
struct Group {
Vector<Node *> nodes;
bool changed;
Group() { changed = false; };
};
enum CurrentProcessType {
CURRENT_PROCESS_TYPE_NONE,
CURRENT_PROCESS_TYPE_PROCESS,
CURRENT_PROCESS_TYPE_PHYSICS_PROCESS,
};
void _setup();
void _cleanup();
void _setup_thread();
void _quit_thread();
void _quit_thread_deferred();
void _cleanup_thread();
void _register_scene_tree_groups();
void _unregister_scene_tree_groups();
void _trigger_process_deferred();
void _handle_process();
void _handle_physics_process();
void _update_group_order(Group &g);
static void _thread_func(void *udata);
void _notification(int p_what);
static void _bind_methods();
Group _process_group;
Group _internal_process_group;
Group _physics_process_group;
Group _internal_physics_process_group;
ProcessMode _process_mode;
Mode _mode;
int _group_flags;
bool _use_priority;
bool _use_threads;
bool _tread_run;
Thread *_thread;
Semaphore _process_semaphore;
Semaphore _main_semaphore;
SafeNumeric<int> _current_process_type;
};
VARIANT_ENUM_CAST(ProcessGroup::ProcessMode);
VARIANT_ENUM_CAST(ProcessGroup::Mode);
VARIANT_ENUM_CAST(ProcessGroup::ProcessGroupFlags);
#endif

View File

@ -640,11 +640,18 @@ bool SceneTree::iteration(float p_time) {
emit_signal("physics_frame"); emit_signal("physics_frame");
// Trigger ProcessGroups processing
call_group_flags(GROUP_CALL_REALTIME, "_pg_process", "trigger_physics_process");
_notify_group_pause("physics_process_internal", Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); _notify_group_pause("physics_process_internal", Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
if (GLOBAL_GET("physics/common/enable_pause_aware_picking")) { if (GLOBAL_GET("physics/common/enable_pause_aware_picking")) {
call_group_flags(GROUP_CALL_REALTIME, "_viewports", "_process_picking", true); call_group_flags(GROUP_CALL_REALTIME, "_viewports", "_process_picking", true);
} }
_notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS); _notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS);
// Wait for all ProcessGroups to finish
call_group_flags(GROUP_CALL_REALTIME, "_pg_process", "wait_physics_process");
_flush_ugc(); _flush_ugc();
MessageQueue::get_singleton()->flush(); //small little hack MessageQueue::get_singleton()->flush(); //small little hack
@ -692,9 +699,15 @@ bool SceneTree::idle(float p_time) {
flush_transform_notifications(); flush_transform_notifications();
// Trigger ProcessGroups processing
call_group_flags(GROUP_CALL_REALTIME, "_pg_process", "trigger_process");
_notify_group_pause("idle_process_internal", Node::NOTIFICATION_INTERNAL_PROCESS); _notify_group_pause("idle_process_internal", Node::NOTIFICATION_INTERNAL_PROCESS);
_notify_group_pause("idle_process", Node::NOTIFICATION_PROCESS); _notify_group_pause("idle_process", Node::NOTIFICATION_PROCESS);
// Wait for all ProcessGroups to finish
call_group_flags(GROUP_CALL_REALTIME, "_pg_process", "wait_process");
Size2 win_size = OS::get_singleton()->get_window_size(); Size2 win_size = OS::get_singleton()->get_window_size();
if (win_size != last_screen_size) { if (win_size != last_screen_size) {

View File

@ -132,6 +132,7 @@
#include "scene/main/canvas_layer.h" #include "scene/main/canvas_layer.h"
#include "scene/main/http_request.h" #include "scene/main/http_request.h"
#include "scene/main/instance_placeholder.h" #include "scene/main/instance_placeholder.h"
#include "scene/main/process_group.h"
#include "scene/main/resource_preloader.h" #include "scene/main/resource_preloader.h"
#include "scene/main/scene_tree.h" #include "scene/main/scene_tree.h"
#include "scene/main/timer.h" #include "scene/main/timer.h"
@ -294,6 +295,7 @@ void register_scene_types() {
ClassDB::register_class<CanvasLayer>(); ClassDB::register_class<CanvasLayer>();
ClassDB::register_class<CanvasModulate>(); ClassDB::register_class<CanvasModulate>();
ClassDB::register_class<ResourcePreloader>(); ClassDB::register_class<ResourcePreloader>();
ClassDB::register_class<ProcessGroup>();
/* REGISTER GUI */ /* REGISTER GUI */
ClassDB::register_class<ButtonGroup>(); ClassDB::register_class<ButtonGroup>();

View File

@ -102,6 +102,9 @@ SceneStringNames::SceneStringNames() {
_physics_process = StaticCString::create("_physics_process"); _physics_process = StaticCString::create("_physics_process");
_process = StaticCString::create("_process"); _process = StaticCString::create("_process");
_process_group_process = StaticCString::create("_process_group_process");
_process_group_physics_process = StaticCString::create("_process_group_physics_process");
_enter_tree = StaticCString::create("_enter_tree"); _enter_tree = StaticCString::create("_enter_tree");
_exit_tree = StaticCString::create("_exit_tree"); _exit_tree = StaticCString::create("_exit_tree");
_enter_world = StaticCString::create("_enter_world"); _enter_world = StaticCString::create("_enter_world");

View File

@ -119,6 +119,8 @@ public:
StringName _physics_process; StringName _physics_process;
StringName _process; StringName _process;
StringName _process_group_process;
StringName _process_group_physics_process;
StringName _enter_world; StringName _enter_world;
StringName _exit_world; StringName _exit_world;
StringName _enter_tree; StringName _enter_tree;