diff options
42 files changed, 886 insertions, 254 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 47d40f2742..8771aa88cc 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -224,6 +224,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const { return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec(); } +void OS::set_delta_smoothing(bool p_enabled) { + ::OS::get_singleton()->set_delta_smoothing(p_enabled); +} + +bool OS::is_delta_smoothing_enabled() const { + return ::OS::get_singleton()->is_delta_smoothing_enabled(); +} + void OS::alert(const String &p_alert, const String &p_title) { ::OS::get_singleton()->alert(p_alert, p_title); } @@ -556,6 +564,9 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec); ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec); + ClassDB::bind_method(D_METHOD("set_delta_smoothing", "delta_smoothing_enabled"), &OS::set_delta_smoothing); + ClassDB::bind_method(D_METHOD("is_delta_smoothing_enabled"), &OS::is_delta_smoothing_enabled); + ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count); ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name); @@ -631,6 +642,7 @@ void OS::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled"); // Those default values need to be specified for the docs generator, // to avoid using values from the documentation writer's own OS instance. diff --git a/core/core_bind.h b/core/core_bind.h index 6083cf2e2f..55c365eb7c 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -141,6 +141,9 @@ public: void set_low_processor_usage_mode_sleep_usec(int p_usec); int get_low_processor_usage_mode_sleep_usec() const; + void set_delta_smoothing(bool p_enabled); + bool is_delta_smoothing_enabled() const; + void alert(const String &p_alert, const String &p_title = "ALERT!"); void crash(const String &p_message); diff --git a/core/extension/make_interface_dumper.py b/core/extension/make_interface_dumper.py index a604112d13..a85d62eff3 100644 --- a/core/extension/make_interface_dumper.py +++ b/core/extension/make_interface_dumper.py @@ -1,9 +1,19 @@ +import zlib + + def run(target, source, env): src = source[0] dst = target[0] - f = open(src, "r", encoding="utf-8") + f = open(src, "rb") g = open(dst, "w", encoding="utf-8") + buf = f.read() + decomp_size = len(buf) + + # Use maximum zlib compression level to further reduce file size + # (at the cost of initial build times). + buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) + g.write( """/* THIS FILE IS GENERATED DO NOT EDIT */ #ifndef GDEXTENSION_INTERFACE_DUMP_H @@ -11,25 +21,32 @@ def run(target, source, env): #ifdef TOOLS_ENABLED +#include "core/io/compression.h" #include "core/io/file_access.h" #include "core/string/ustring.h" -class GDExtensionInterfaceDump { - private: - static constexpr char const *gdextension_interface_dump =""" +""" ) - for line in f: - g.write('"' + line.rstrip().replace('"', '\\"') + '\\n"\n') - g.write(";\n") + + g.write("static const int _gdextension_interface_data_compressed_size = " + str(len(buf)) + ";\n") + g.write("static const int _gdextension_interface_data_uncompressed_size = " + str(decomp_size) + ";\n") + g.write("static const unsigned char _gdextension_interface_data_compressed[] = {\n") + for i in range(len(buf)): + g.write("\t" + str(buf[i]) + ",\n") + g.write("};\n") g.write( """ +class GDExtensionInterfaceDump { public: static void generate_gdextension_interface_file(const String &p_path) { Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE); ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path)); - CharString cs(gdextension_interface_dump); - fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); + Vector<uint8_t> data; + data.resize(_gdextension_interface_data_uncompressed_size); + int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE); + ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt."); + fa->store_buffer(data.ptr(), data.size()); }; }; diff --git a/core/input/input.cpp b/core/input/input.cpp index 2b3e0b56e4..1348389481 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -542,6 +542,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em Ref<InputEventScreenTouch> touch_event; touch_event.instantiate(); touch_event->set_pressed(mb->is_pressed()); + touch_event->set_canceled(mb->is_canceled()); touch_event->set_position(mb->get_position()); touch_event->set_double_tap(mb->is_double_click()); touch_event->set_device(InputEvent::DEVICE_ID_EMULATION); @@ -613,6 +614,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em button_event->set_position(st->get_position()); button_event->set_global_position(st->get_position()); button_event->set_pressed(st->is_pressed()); + button_event->set_canceled(st->is_canceled()); button_event->set_button_index(MouseButton::LEFT); button_event->set_double_click(st->is_double_tap()); diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 19e4d6182a..95be01bc65 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -52,15 +52,15 @@ bool InputEvent::is_action(const StringName &p_action, bool p_exact_match) const } bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const { - bool pressed; - bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed, nullptr, nullptr); - return valid && pressed && (p_allow_echo || !is_echo()); + bool pressed_state; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); + return valid && pressed_state && (p_allow_echo || !is_echo()); } bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match) const { - bool pressed; - bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed, nullptr, nullptr); - return valid && !pressed; + bool pressed_state; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); + return valid && !pressed_state; } float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match) const { @@ -75,8 +75,16 @@ float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exa return valid ? raw_strength : 0.0f; } +bool InputEvent::is_canceled() const { + return canceled; +} + bool InputEvent::is_pressed() const { - return false; + return pressed && !canceled; +} + +bool InputEvent::is_released() const { + return !pressed && !canceled; } bool InputEvent::is_echo() const { @@ -108,7 +116,9 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::is_action_released, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::get_action_strength, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_canceled"), &InputEvent::is_canceled); ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed); + ClassDB::bind_method(D_METHOD("is_released"), &InputEvent::is_released); ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo); ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text); @@ -318,10 +328,6 @@ void InputEventKey::set_pressed(bool p_pressed) { emit_changed(); } -bool InputEventKey::is_pressed() const { - return pressed; -} - void InputEventKey::set_keycode(Key p_keycode) { keycode = p_keycode; emit_changed(); @@ -671,8 +677,8 @@ void InputEventMouseButton::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventMouseButton::is_pressed() const { - return pressed; +void InputEventMouseButton::set_canceled(bool p_canceled) { + canceled = p_canceled; } void InputEventMouseButton::set_double_click(bool p_double_click) { @@ -699,6 +705,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co mb->set_button_mask(get_button_mask()); mb->set_pressed(pressed); + mb->set_canceled(canceled); mb->set_double_click(double_click); mb->set_factor(factor); mb->set_button_index(button_index); @@ -794,6 +801,7 @@ String InputEventMouseButton::as_text() const { String InputEventMouseButton::to_string() { String p = is_pressed() ? "true" : "false"; + String canceled_state = is_canceled() ? "true" : "false"; String d = double_click ? "true" : "false"; MouseButton idx = get_button_index(); @@ -820,7 +828,7 @@ String InputEventMouseButton::to_string() { // Work around the fact vformat can only take 5 substitutions but 6 need to be passed. String index_and_mods = vformat("button_index=%s, mods=%s", button_index, mods); - return vformat("InputEventMouseButton: %s, pressed=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, String(get_position()), get_button_mask(), d); + return vformat("InputEventMouseButton: %s, pressed=%s, canceled=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, canceled_state, String(get_position()), get_button_mask(), d); } void InputEventMouseButton::_bind_methods() { @@ -831,13 +839,14 @@ void InputEventMouseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_button_index"), &InputEventMouseButton::get_button_index); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventMouseButton::set_pressed); - // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventMouseButton::is_pressed); + ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventMouseButton::set_canceled); ClassDB::bind_method(D_METHOD("set_double_click", "double_click"), &InputEventMouseButton::set_double_click); ClassDB::bind_method(D_METHOD("is_double_click"), &InputEventMouseButton::is_double_click); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "factor"), "set_factor", "get_factor"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_click"), "set_double_click", "is_double_click"); } @@ -945,6 +954,10 @@ bool InputEventMouseMotion::accumulate(const Ref<InputEvent> &p_event) { return false; } + if (is_canceled() != motion->is_canceled()) { + return false; + } + if (is_pressed() != motion->is_pressed()) { return false; } @@ -1015,6 +1028,7 @@ JoyAxis InputEventJoypadMotion::get_axis() const { void InputEventJoypadMotion::set_axis_value(float p_value) { axis_value = p_value; + pressed = Math::abs(axis_value) >= 0.5f; emit_changed(); } @@ -1022,10 +1036,6 @@ float InputEventJoypadMotion::get_axis_value() const { return axis_value; } -bool InputEventJoypadMotion::is_pressed() const { - return Math::abs(axis_value) >= 0.5f; -} - bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref<InputEventJoypadMotion> jm = p_event; if (jm.is_null()) { @@ -1040,12 +1050,12 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool p if (match) { float jm_abs_axis_value = Math::abs(jm->get_axis_value()); bool same_direction = (((axis_value < 0) == (jm->axis_value < 0)) || jm->axis_value == 0); - bool pressed = same_direction && jm_abs_axis_value >= p_deadzone; + bool pressed_state = same_direction && jm_abs_axis_value >= p_deadzone; if (r_pressed != nullptr) { - *r_pressed = pressed; + *r_pressed = pressed_state; } if (r_strength != nullptr) { - if (pressed) { + if (pressed_state) { if (p_deadzone == 1.0f) { *r_strength = 1.0f; } else { @@ -1125,10 +1135,6 @@ void InputEventJoypadButton::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventJoypadButton::is_pressed() const { - return pressed; -} - void InputEventJoypadButton::set_pressure(float p_pressure) { pressure = p_pressure; } @@ -1209,7 +1215,7 @@ String InputEventJoypadButton::as_text() const { } String InputEventJoypadButton::to_string() { - String p = pressed ? "true" : "false"; + String p = is_pressed() ? "true" : "false"; return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f", button_index, p, pressure); } @@ -1229,7 +1235,6 @@ void InputEventJoypadButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pressure"), &InputEventJoypadButton::get_pressure); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventJoypadButton::set_pressed); - // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventJoypadButton::is_pressed); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure"), "set_pressure", "get_pressure"); @@ -1258,8 +1263,8 @@ void InputEventScreenTouch::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventScreenTouch::is_pressed() const { - return pressed; +void InputEventScreenTouch::set_canceled(bool p_canceled) { + canceled = p_canceled; } void InputEventScreenTouch::set_double_tap(bool p_double_tap) { @@ -1277,21 +1282,23 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co st->set_index(index); st->set_position(p_xform.xform(pos + p_local_ofs)); st->set_pressed(pressed); + st->set_canceled(canceled); st->set_double_tap(double_tap); return st; } String InputEventScreenTouch::as_text() const { - String status = pressed ? RTR("touched") : RTR("released"); + String status = canceled ? RTR("canceled") : (pressed ? RTR("touched") : RTR("released")); return vformat(RTR("Screen %s at (%s) with %s touch points"), status, String(get_position()), itos(index)); } String InputEventScreenTouch::to_string() { String p = pressed ? "true" : "false"; + String canceled_state = canceled ? "true" : "false"; String double_tap_string = double_tap ? "true" : "false"; - return vformat("InputEventScreenTouch: index=%d, pressed=%s, position=(%s), double_tap=%s", index, p, String(get_position()), double_tap_string); + return vformat("InputEventScreenTouch: index=%d, pressed=%s, canceled=%s, position=(%s), double_tap=%s", index, p, canceled_state, String(get_position()), double_tap_string); } void InputEventScreenTouch::_bind_methods() { @@ -1302,13 +1309,14 @@ void InputEventScreenTouch::_bind_methods() { ClassDB::bind_method(D_METHOD("get_position"), &InputEventScreenTouch::get_position); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventScreenTouch::set_pressed); - //ClassDB::bind_method(D_METHOD("is_pressed"),&InputEventScreenTouch::is_pressed); + ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventScreenTouch::set_canceled); ClassDB::bind_method(D_METHOD("set_double_tap", "double_tap"), &InputEventScreenTouch::set_double_tap); ClassDB::bind_method(D_METHOD("is_double_tap"), &InputEventScreenTouch::is_double_tap); ADD_PROPERTY(PropertyInfo(Variant::INT, "index"), "set_index", "get_index"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_tap"), "set_double_tap", "is_double_tap"); } @@ -1460,10 +1468,6 @@ void InputEventAction::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventAction::is_pressed() const { - return pressed; -} - void InputEventAction::set_strength(float p_strength) { strength = CLAMP(p_strength, 0.0f, 1.0f); } @@ -1492,7 +1496,7 @@ bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool p_exact bool match = action == act->action; if (match) { - bool act_pressed = act->pressed; + bool act_pressed = act->is_pressed(); if (r_pressed != nullptr) { *r_pressed = act_pressed; } @@ -1523,7 +1527,7 @@ String InputEventAction::as_text() const { } String InputEventAction::to_string() { - String p = pressed ? "true" : "false"; + String p = is_pressed() ? "true" : "false"; return vformat("InputEventAction: action=\"%s\", pressed=%s", action, p); } @@ -1532,13 +1536,10 @@ void InputEventAction::_bind_methods() { ClassDB::bind_method(D_METHOD("get_action"), &InputEventAction::get_action); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventAction::set_pressed); - //ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventAction::is_pressed); ClassDB::bind_method(D_METHOD("set_strength", "strength"), &InputEventAction::set_strength); ClassDB::bind_method(D_METHOD("get_strength"), &InputEventAction::get_strength); - // ClassDB::bind_method(D_METHOD("is_action", "name"), &InputEventAction::is_action); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "action"), "set_action", "get_action"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_strength", "get_strength"); @@ -1761,10 +1762,6 @@ void InputEventShortcut::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); } -bool InputEventShortcut::is_pressed() const { - return true; -} - String InputEventShortcut::as_text() const { ERR_FAIL_COND_V(shortcut.is_null(), "None"); diff --git a/core/input/input_event.h b/core/input/input_event.h index 4be42d0bd2..59a87239bd 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -56,6 +56,9 @@ class InputEvent : public Resource { int device = 0; protected: + bool canceled = false; + bool pressed = false; + static void _bind_methods(); public: @@ -71,8 +74,9 @@ public: float get_action_strength(const StringName &p_action, bool p_exact_match = false) const; float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const; - // To be removed someday, since they do not make sense for all events - virtual bool is_pressed() const; + bool is_canceled() const; + bool is_pressed() const; + bool is_released() const; virtual bool is_echo() const; virtual String as_text() const = 0; @@ -149,8 +153,6 @@ public: class InputEventKey : public InputEventWithModifiers { GDCLASS(InputEventKey, InputEventWithModifiers); - bool pressed = false; /// otherwise release - Key keycode = Key::NONE; // Key enum, without modifier masks. Key physical_keycode = Key::NONE; Key key_label = Key::NONE; @@ -163,7 +165,6 @@ protected: public: void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_keycode(Key p_keycode); Key get_keycode() const; @@ -229,7 +230,6 @@ class InputEventMouseButton : public InputEventMouse { float factor = 1; MouseButton button_index = MouseButton::NONE; - bool pressed = false; //otherwise released bool double_click = false; //last even less than double click time protected: @@ -243,7 +243,7 @@ public: MouseButton get_button_index() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; + void set_canceled(bool p_canceled); void set_double_click(bool p_double_click); bool is_double_click() const; @@ -312,8 +312,6 @@ public: void set_axis_value(float p_value); float get_axis_value() const; - virtual bool is_pressed() const override; - virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override; @@ -328,7 +326,6 @@ class InputEventJoypadButton : public InputEvent { GDCLASS(InputEventJoypadButton, InputEvent); JoyButton button_index = (JoyButton)0; - bool pressed = false; float pressure = 0; //0 to 1 protected: static void _bind_methods(); @@ -338,7 +335,6 @@ public: JoyButton get_button_index() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_pressure(float p_pressure); float get_pressure() const; @@ -360,7 +356,6 @@ class InputEventScreenTouch : public InputEventFromWindow { GDCLASS(InputEventScreenTouch, InputEventFromWindow); int index = 0; Vector2 pos; - bool pressed = false; bool double_tap = false; protected: @@ -374,7 +369,7 @@ public: Vector2 get_position() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; + void set_canceled(bool p_canceled); void set_double_tap(bool p_double_tap); bool is_double_tap() const; @@ -434,7 +429,6 @@ class InputEventAction : public InputEvent { GDCLASS(InputEventAction, InputEvent); StringName action; - bool pressed = false; float strength = 1.0f; protected: @@ -445,7 +439,6 @@ public: StringName get_action() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_strength(float p_strength); float get_strength() const; @@ -569,7 +562,6 @@ protected: public: void set_shortcut(Ref<Shortcut> p_shortcut); Ref<Shortcut> get_shortcut(); - virtual bool is_pressed() const override; virtual String as_text() const override; virtual String to_string() override; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index f852e8d382..c68191554c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -476,9 +476,6 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, if (run_on_current_thread) { load_task_ptr->thread_id = Thread::get_caller_id(); - if (must_not_register) { - load_token->res_if_unregistered = load_task_ptr->resource; - } } else { load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); } @@ -486,6 +483,9 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, if (run_on_current_thread) { _thread_load_function(load_task_ptr); + if (must_not_register) { + load_token->res_if_unregistered = load_task_ptr->resource; + } } return load_token; @@ -613,14 +613,33 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro return Ref<Resource>(); } - if (load_task.task_id != 0 && !load_task.awaited) { - // Loading thread is in the worker pool and still not awaited. + if (load_task.task_id != 0) { + // Loading thread is in the worker pool. load_task.awaited = true; thread_load_mutex.unlock(); - WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); - thread_load_mutex.lock(); + Error err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + if (err == ERR_BUSY) { + // The WorkerThreadPool has scheduled tasks in a way that the current load depends on + // another one in a lower stack frame. Restart such load here. When the stack is eventually + // unrolled, the original load will have been notified to go on. +#ifdef DEV_ENABLED + print_verbose("ResourceLoader: Load task happened to wait on another one deep in the call stack. Attempting to avoid deadlock by re-issuing the load now."); +#endif + // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's + // an ongoing load for that resource and wait for it again. This value forces a new load. + Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); + Ref<Resource> resource = _load_complete(*token.ptr(), &err); + if (r_error) { + *r_error = err; + } + thread_load_mutex.lock(); + return resource; + } else { + DEV_ASSERT(err == OK); + thread_load_mutex.lock(); + } } else { - // Loading thread is main or user thread, or in the worker pool, but already awaited by some other thread. + // Loading thread is main or user thread. if (!load_task.cond_var) { load_task.cond_var = memnew(ConditionVariable); } diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h index cf0bd47bce..58706fb9a9 100644 --- a/core/object/ref_counted.h +++ b/core/object/ref_counted.h @@ -97,26 +97,15 @@ public: return reference != p_r.reference; } - _FORCE_INLINE_ T *operator->() { + _FORCE_INLINE_ T *operator*() const { return reference; } - _FORCE_INLINE_ T *operator*() { + _FORCE_INLINE_ T *operator->() const { return reference; } - _FORCE_INLINE_ const T *operator->() const { - return reference; - } - - _FORCE_INLINE_ const T *ptr() const { - return reference; - } - _FORCE_INLINE_ T *ptr() { - return reference; - } - - _FORCE_INLINE_ const T *operator*() const { + _FORCE_INLINE_ T *ptr() const { return reference; } diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index e59ab3d6ae..afe6ecd1b3 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -51,6 +51,23 @@ void WorkerThreadPool::_process_task_queue() { void WorkerThreadPool::_process_task(Task *p_task) { bool low_priority = p_task->low_priority; + int pool_thread_index = -1; + Task *prev_low_prio_task = nullptr; // In case this is recursively called. + + if (!use_native_low_priority_threads) { + pool_thread_index = thread_ids[Thread::get_caller_id()]; + ThreadData &curr_thread = threads[pool_thread_index]; + task_mutex.lock(); + p_task->pool_thread_index = pool_thread_index; + if (low_priority) { + low_priority_tasks_running++; + prev_low_prio_task = curr_thread.current_low_prio_task; + curr_thread.current_low_prio_task = p_task; + } else { + curr_thread.current_low_prio_task = nullptr; + } + task_mutex.unlock(); + } if (p_task->group) { // Handling a group @@ -126,21 +143,36 @@ void WorkerThreadPool::_process_task(Task *p_task) { p_task->callable.callp(nullptr, 0, ret, ce); } + task_mutex.lock(); p_task->completed = true; - p_task->done_semaphore.post(); + for (uint8_t i = 0; i < p_task->waiting; i++) { + p_task->done_semaphore.post(); + } + if (!use_native_low_priority_threads) { + p_task->pool_thread_index = -1; + } + task_mutex.unlock(); // Keep mutex down to here since on unlock the task may be freed. } - if (!use_native_low_priority_threads && low_priority) { - // A low prioriry task was freed, so see if we can move a pending one to the high priority queue. + // Task may have been freed by now (all callers notified). + p_task = nullptr; + + if (!use_native_low_priority_threads) { bool post = false; task_mutex.lock(); - if (low_priority_task_queue.first()) { - Task *low_prio_task = low_priority_task_queue.first()->self(); - low_priority_task_queue.remove(low_priority_task_queue.first()); - task_queue.add_last(&low_prio_task->task_elem); - post = true; - } else { + ThreadData &curr_thread = threads[pool_thread_index]; + curr_thread.current_low_prio_task = prev_low_prio_task; + if (low_priority) { low_priority_threads_used--; + low_priority_tasks_running--; + // A low prioriry task was freed, so see if we can move a pending one to the high priority queue. + if (_try_promote_low_priority_task()) { + post = true; + } + + if (low_priority_tasks_awaiting_others == low_priority_tasks_running) { + _prevent_low_prio_saturation_deadlock(); + } } task_mutex.unlock(); if (post) { @@ -198,6 +230,35 @@ void WorkerThreadPool::_post_task(Task *p_task, bool p_high_priority) { } } +bool WorkerThreadPool::_try_promote_low_priority_task() { + if (low_priority_task_queue.first()) { + Task *low_prio_task = low_priority_task_queue.first()->self(); + low_priority_task_queue.remove(low_priority_task_queue.first()); + task_queue.add_last(&low_prio_task->task_elem); + low_priority_threads_used++; + return true; + } else { + return false; + } +} + +void WorkerThreadPool::_prevent_low_prio_saturation_deadlock() { + if (low_priority_tasks_awaiting_others == low_priority_tasks_running) { +#ifdef DEV_ENABLED + print_verbose("WorkerThreadPool: Low-prio slots saturated with tasks all waiting for other low-prio tasks. Attempting to avoid deadlock by scheduling one extra task."); +#endif + // In order not to create dependency cycles, we can only schedule the next one. + // We'll keep doing the same until the deadlock is broken, + SelfList<Task> *to_promote = low_priority_task_queue.first(); + if (to_promote) { + low_priority_task_queue.remove(to_promote); + task_queue.add_last(to_promote); + low_priority_threads_used++; + task_available_semaphore.post(); + } + } +} + WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void *), void *p_userdata, bool p_high_priority, const String &p_description) { return _add_task(Callable(), p_func, p_userdata, nullptr, p_high_priority, p_description); } @@ -238,66 +299,113 @@ bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const { return completed; } -void WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { +Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { task_mutex.lock(); Task **taskp = tasks.getptr(p_task_id); if (!taskp) { task_mutex.unlock(); - ERR_FAIL_MSG("Invalid Task ID"); // Invalid task + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid Task ID"); // Invalid task } Task *task = *taskp; - if (task->waiting) { - String description = task->description; - task_mutex.unlock(); - if (description.is_empty()) { - ERR_FAIL_MSG("Another thread is waiting on this task: " + itos(p_task_id)); // Invalid task - } else { - ERR_FAIL_MSG("Another thread is waiting on this task: " + description + " (" + itos(p_task_id) + ")"); // Invalid task + if (!task->completed) { + if (!use_native_low_priority_threads && task->pool_thread_index != -1) { // Otherwise, it's not running yet. + int caller_pool_th_index = thread_ids.has(Thread::get_caller_id()) ? thread_ids[Thread::get_caller_id()] : -1; + if (caller_pool_th_index == task->pool_thread_index) { + // Deadlock prevention. + // Waiting for a task run on this same thread? That means the task to be awaited started waiting as well + // and another task was run to make use of the thread in the meantime, with enough bad luck as to + // the need to wait for the original task arose in turn. + // In other words, the task we want to wait for is buried in the stack. + // Let's report the caller about the issue to it handles as it sees fit. + task_mutex.unlock(); + return ERR_BUSY; + } } - } - - task->waiting = true; - task_mutex.unlock(); + task->waiting++; + + bool is_low_prio_waiting_for_another = false; + if (!use_native_low_priority_threads) { + // Deadlock prevention: + // If all low-prio tasks are waiting for other low-prio tasks and there are no more free low-prio slots, + // we have a no progressable situation. We can apply a workaround, consisting in promoting an awaited queued + // low-prio task to the schedule queue so it can run and break the "impasse". + // NOTE: A similar reasoning could be made about high priority tasks, but there are usually much more + // than low-prio. Therefore, a deadlock there would only happen when dealing with a very complex task graph + // or when there are too few worker threads (limited platforms or exotic settings). If that turns out to be + // an issue in the real world, a further fix can be applied against that. + if (task->low_priority) { + bool awaiter_is_a_low_prio_task = thread_ids.has(Thread::get_caller_id()) && threads[thread_ids[Thread::get_caller_id()]].current_low_prio_task; + if (awaiter_is_a_low_prio_task) { + is_low_prio_waiting_for_another = true; + low_priority_tasks_awaiting_others++; + if (low_priority_tasks_awaiting_others == low_priority_tasks_running) { + _prevent_low_prio_saturation_deadlock(); + } + } + } + } - if (use_native_low_priority_threads && task->low_priority) { - task->low_priority_thread->wait_to_finish(); + task_mutex.unlock(); - task_mutex.lock(); - native_thread_allocator.free(task->low_priority_thread); - } else { - int *index = thread_ids.getptr(Thread::get_caller_id()); - - if (index) { - // We are an actual process thread, we must not be blocked so continue processing stuff if available. - bool must_exit = false; - while (true) { - if (task->done_semaphore.try_wait()) { - // If done, exit - break; - } - if (!must_exit && task_available_semaphore.try_wait()) { - if (exit_threads) { - must_exit = true; - } else { - // Solve tasks while they are around. - _process_task_queue(); - continue; + if (use_native_low_priority_threads && task->low_priority) { + task->done_semaphore.wait(); + } else { + bool current_is_pool_thread = thread_ids.has(Thread::get_caller_id()); + if (current_is_pool_thread) { + // We are an actual process thread, we must not be blocked so continue processing stuff if available. + bool must_exit = false; + while (true) { + if (task->done_semaphore.try_wait()) { + // If done, exit + break; } + if (!must_exit) { + if (task_available_semaphore.try_wait()) { + if (exit_threads) { + must_exit = true; + } else { + // Solve tasks while they are around. + _process_task_queue(); + continue; + } + } else if (!use_native_low_priority_threads && task->low_priority) { + // A low prioriry task started waiting, so see if we can move a pending one to the high priority queue. + task_mutex.lock(); + bool post = _try_promote_low_priority_task(); + task_mutex.unlock(); + if (post) { + task_available_semaphore.post(); + } + } + } + OS::get_singleton()->delay_usec(1); // Microsleep, this could be converted to waiting for multiple objects in supported platforms for a bit more performance. } - OS::get_singleton()->delay_usec(1); // Microsleep, this could be converted to waiting for multiple objects in supported platforms for a bit more performance. + } else { + task->done_semaphore.wait(); } - } else { - task->done_semaphore.wait(); } task_mutex.lock(); + if (is_low_prio_waiting_for_another) { + low_priority_tasks_awaiting_others--; + } + + task->waiting--; + } + + if (task->waiting == 0) { + if (use_native_low_priority_threads && task->low_priority) { + task->low_priority_thread->wait_to_finish(); + native_thread_allocator.free(task->low_priority_thread); + } + tasks.erase(p_task_id); + task_allocator.free(task); } - tasks.erase(p_task_id); - task_allocator.free(task); task_mutex.unlock(); + return OK; } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { @@ -429,7 +537,7 @@ void WorkerThreadPool::init(int p_thread_count, bool p_use_native_threads_low_pr if (p_use_native_threads_low_priority) { max_low_priority_threads = 0; } else { - max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count); + max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1); } use_native_low_priority_threads = p_use_native_threads_low_priority; diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index d47c6ad714..d4d9387765 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -81,10 +81,11 @@ private: bool completed = false; Group *group = nullptr; SelfList<Task> task_elem; - bool waiting = false; // Waiting for completion + uint32_t waiting = 0; bool low_priority = false; BaseTemplateUserdata *template_userdata = nullptr; Thread *low_priority_thread = nullptr; + int pool_thread_index = -1; void free_template_userdata(); Task() : @@ -104,6 +105,7 @@ private: struct ThreadData { uint32_t index; Thread thread; + Task *current_low_prio_task = nullptr; }; TightLocalVector<ThreadData> threads; @@ -116,6 +118,8 @@ private: bool use_native_low_priority_threads = false; uint32_t max_low_priority_threads = 0; uint32_t low_priority_threads_used = 0; + uint32_t low_priority_tasks_running = 0; + uint32_t low_priority_tasks_awaiting_others = 0; uint64_t last_task = 1; @@ -127,6 +131,9 @@ private: void _post_task(Task *p_task, bool p_high_priority); + bool _try_promote_low_priority_task(); + void _prevent_low_prio_saturation_deadlock(); + static WorkerThreadPool *singleton; TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description); @@ -169,7 +176,7 @@ public: TaskID add_task(const Callable &p_action, bool p_high_priority = false, const String &p_description = String()); bool is_task_completed(TaskID p_task_id) const; - void wait_for_task_completion(TaskID p_task_id); + Error wait_for_task_completion(TaskID p_task_id); template <class C, class M, class U> GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) { diff --git a/core/os/os.cpp b/core/os/os.cpp index 4123a1d602..025dcfe982 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -151,6 +151,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const { return low_processor_usage_mode_sleep_usec; } +void OS::set_delta_smoothing(bool p_enabled) { + _delta_smoothing_enabled = p_enabled; +} + +bool OS::is_delta_smoothing_enabled() const { + return _delta_smoothing_enabled; +} + String OS::get_executable_path() const { return _execpath; } diff --git a/core/os/os.h b/core/os/os.h index 1652c1ed90..09ed31b9ce 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -52,6 +52,7 @@ class OS { bool _keep_screen_on = true; // set default value to true, because this had been true before godot 2.0. bool low_processor_usage_mode = false; int low_processor_usage_mode_sleep_usec = 10000; + bool _delta_smoothing_enabled = false; bool _verbose_stdout = false; bool _debug_stdout = false; String _local_clipboard; @@ -154,6 +155,9 @@ public: virtual void set_low_processor_usage_mode_sleep_usec(int p_usec); virtual int get_low_processor_usage_mode_sleep_usec() const; + void set_delta_smoothing(bool p_enabled); + bool is_delta_smoothing_enabled() const; + virtual Vector<String> get_system_fonts() const { return Vector<String>(); }; virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); }; virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }; diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h index 8669bcaeeb..bfc9f6fc9a 100644 --- a/core/templates/safe_refcount.h +++ b/core/templates/safe_refcount.h @@ -50,11 +50,14 @@ // value and, as an important benefit, you can be sure the value is properly synchronized // even with threads that are already running. -// This is used in very specific areas of the engine where it's critical that these guarantees are held +// These are used in very specific areas of the engine where it's critical that these guarantees are held #define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \ static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type)); \ static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \ static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value); +#define SAFE_FLAG_TYPE_PUN_GUARANTEES \ + static_assert(sizeof(SafeFlag) == sizeof(bool)); \ + static_assert(alignof(SafeFlag) == alignof(bool)); template <class T> class SafeNumeric { diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index 9e2e115301..5d2dc8942f 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -71,6 +71,12 @@ Returns [code]true[/code] if this input event's type is one that can be assigned to an input action. </description> </method> + <method name="is_canceled" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if this input event has been canceled. + </description> + </method> <method name="is_echo" qualifiers="const"> <return type="bool" /> <description> @@ -93,6 +99,12 @@ [b]Note:[/b] Due to keyboard ghosting, [method is_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information. </description> </method> + <method name="is_released" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if this input event is released. Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag]. + </description> + </method> <method name="xformed_by" qualifiers="const"> <return type="InputEvent" /> <param index="0" name="xform" type="Transform2D" /> diff --git a/doc/classes/InputEventMouseButton.xml b/doc/classes/InputEventMouseButton.xml index ebed234f2f..500244f209 100644 --- a/doc/classes/InputEventMouseButton.xml +++ b/doc/classes/InputEventMouseButton.xml @@ -13,6 +13,9 @@ <member name="button_index" type="int" setter="set_button_index" getter="get_button_index" enum="MouseButton" default="0"> The mouse button identifier, one of the [enum MouseButton] button or button wheel constants. </member> + <member name="canceled" type="bool" setter="set_canceled" getter="is_canceled" default="false"> + If [code]true[/code], the mouse button event has been canceled. + </member> <member name="double_click" type="bool" setter="set_double_click" getter="is_double_click" default="false"> If [code]true[/code], the mouse button's state is a double-click. </member> diff --git a/doc/classes/InputEventScreenTouch.xml b/doc/classes/InputEventScreenTouch.xml index d7230a6814..2f993d151c 100644 --- a/doc/classes/InputEventScreenTouch.xml +++ b/doc/classes/InputEventScreenTouch.xml @@ -11,6 +11,9 @@ <link title="InputEvent">$DOCS_URL/tutorials/inputs/inputevent.html</link> </tutorials> <members> + <member name="canceled" type="bool" setter="set_canceled" getter="is_canceled" default="false"> + If [code]true[/code], the touch event has been canceled. + </member> <member name="double_tap" type="bool" setter="set_double_tap" getter="is_double_tap" default="false"> If [code]true[/code], the touch's state is a double tap. </member> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 3bf4177549..e64b409551 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -670,6 +670,9 @@ </method> </methods> <members> + <member name="delta_smoothing" type="bool" setter="set_delta_smoothing" getter="is_delta_smoothing_enabled" default="true"> + If [code]true[/code], the engine filters the time delta measured between each frame, and attempts to compensate for random variation. This will only operate on systems where V-Sync is active. + </member> <member name="low_processor_usage_mode" type="bool" setter="set_low_processor_usage_mode" getter="is_in_low_processor_usage_mode" default="false"> If [code]true[/code], the engine optimizes for low processor usage by only refreshing the screen if needed. Can improve battery consumption on mobile. </member> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 818e402067..ee3367c24c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -284,6 +284,11 @@ <member name="application/config/windows_native_icon" type="String" setter="" getter="" default=""""> Icon set in [code].ico[/code] format used on Windows to set the game's icon. This is done automatically on start by calling [method DisplayServer.set_native_icon]. </member> + <member name="application/run/delta_smoothing" type="bool" setter="" getter="" default="true"> + Time samples for frame deltas are subject to random variation introduced by the platform, even when frames are displayed at regular intervals thanks to V-Sync. This can lead to jitter. Delta smoothing can often give a better result by filtering the input deltas to correct for minor fluctuations from the refresh rate. + [b]Note:[/b] Delta smoothing is only attempted when [member display/window/vsync/vsync_mode] is set to [code]enabled[/code], as it does not work well without V-Sync. + It may take several seconds at a stable frame rate before the smoothing is initially activated. It will only be active on machines where performance is adequate to render frames at the refresh rate. + </member> <member name="application/run/disable_stderr" type="bool" setter="" getter="" default="false"> If [code]true[/code], disables printing to standard error. If [code]true[/code], this also hides error and warning messages printed by [method @GlobalScope.push_error] and [method @GlobalScope.push_warning]. See also [member application/run/disable_stdout]. Changes to this setting will only be applied upon restarting the application. diff --git a/doc/classes/WorkerThreadPool.xml b/doc/classes/WorkerThreadPool.xml index ace5f95506..136c6279d7 100644 --- a/doc/classes/WorkerThreadPool.xml +++ b/doc/classes/WorkerThreadPool.xml @@ -100,10 +100,13 @@ </description> </method> <method name="wait_for_task_completion"> - <return type="void" /> + <return type="int" enum="Error" /> <param index="0" name="task_id" type="int" /> <description> Pauses the thread that calls this method until the task with the given ID is completed. + Returns [constant @GlobalScope.OK] if the task could be successfully awaited. + Returns [constant @GlobalScope.ERR_INVALID_PARAMETER] if a task with the passed ID does not exist (maybe because it was already awaited and disposed of). + Returns [constant @GlobalScope.ERR_BUSY] if the call is made from another running task and, due to task scheduling, the task to await is at a lower level in the call stack and therefore can't progress. This is an advanced situation that should only matter when some tasks depend on others. </description> </method> </methods> diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 05a024f913..2a04f7b174 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4592,6 +4592,9 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); for (const KeyValue<Node *, Object *> &E : selection) { Node2D *n2d = Object::cast_to<Node2D>(E.key); + if (!n2d) { + continue; + } Bone2D *new_bone = memnew(Bone2D); String new_bone_name = n2d->get_name(); diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index cae055c6c5..af5843d6cf 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -439,6 +439,7 @@ bool ProjectConverter3To4::convert() { rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines); rename_input_map_scancode(source_lines, reg_container); rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, source_lines); + custom_rename(source_lines, "config_version=4", "config_version=5"); } else if (file_name.ends_with(".csproj")) { // TODO } else if (file_name.ends_with(".import")) { diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 9f9e321a9c..52e6b478f9 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -2272,14 +2272,6 @@ void ProjectManager::_perform_full_project_conversion() { const String &path = selected_list[0].path; print_line("Converting project: " + path); - - Ref<ConfigFile> cf; - cf.instantiate(); - cf->load(path.path_join("project.godot")); - cf->set_value("", "config_version", GODOT4_CONFIG_VERSION); - cf->save(path.path_join("project.godot")); - _project_list->set_project_version(path, GODOT4_CONFIG_VERSION); - List<String> args; args.push_back("--path"); args.push_back(path); @@ -2287,6 +2279,8 @@ void ProjectManager::_perform_full_project_conversion() { Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); + + _project_list->set_project_version(path, GODOT4_CONFIG_VERSION); } void ProjectManager::_run_project_confirm() { diff --git a/main/main.cpp b/main/main.cpp index f9f2449721..17e4f69ef2 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -470,6 +470,7 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n"); OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n"); OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); + OS::get_singleton()->print(" --delta-smoothing <enable> Enable or disable frame delta smoothing ['enable', 'disable'].\n"); OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n"); OS::get_singleton()->print("\n"); @@ -794,6 +795,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Vector<String> breakpoints; bool use_custom_res = true; bool force_res = false; + bool delta_smoothing_override = false; String default_renderer = ""; String default_renderer_mobile = ""; @@ -1003,6 +1005,29 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->print("Missing tablet driver argument, aborting.\n"); goto error; } + } else if (I->get() == "--delta-smoothing") { + if (I->next()) { + String string = I->next()->get(); + bool recognised = false; + if (string == "enable") { + OS::get_singleton()->set_delta_smoothing(true); + delta_smoothing_override = true; + recognised = true; + } + if (string == "disable") { + OS::get_singleton()->set_delta_smoothing(false); + delta_smoothing_override = false; + recognised = true; + } + if (!recognised) { + OS::get_singleton()->print("Delta-smoothing argument not recognised, aborting.\n"); + goto error; + } + N = I->next()->next(); + } else { + OS::get_singleton()->print("Missing delta-smoothing argument, aborting.\n"); + goto error; + } } else if (I->get() == "--single-window") { // force single window single_window = true; @@ -1930,6 +1955,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->set_low_processor_usage_mode_sleep_usec( GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater"), 6900)); // Roughly 144 FPS + GLOBAL_DEF("application/run/delta_smoothing", true); + if (!delta_smoothing_override) { + OS::get_singleton()->set_delta_smoothing(GLOBAL_GET("application/run/delta_smoothing")); + } + GLOBAL_DEF("display/window/ios/allow_high_refresh_rate", true); GLOBAL_DEF("display/window/ios/hide_home_indicator", true); GLOBAL_DEF("display/window/ios/hide_status_bar", true); diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index 6441a403f4..569930d427 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -30,6 +30,9 @@ #include "main_timer_sync.h" +#include "core/os/os.h" +#include "servers/display_server.h" + void MainFrameTime::clamp_process_step(double min_process_step, double max_process_step) { if (process_step < min_process_step) { process_step = min_process_step; @@ -40,6 +43,258 @@ void MainFrameTime::clamp_process_step(double min_process_step, double max_proce ///////////////////////////////// +void MainTimerSync::DeltaSmoother::update_refresh_rate_estimator(int64_t p_delta) { + // the calling code should prevent 0 or negative values of delta + // (preventing divide by zero) + + // note that if the estimate gets locked, and something external changes this + // (e.g. user changes to non-vsync in the OS), then the results may be less than ideal, + // but usually it will detect this via the FPS measurement and not attempt smoothing. + // This should be a rare occurrence anyway, and will be cured next time user restarts game. + if (_estimate_locked) { + return; + } + + // First average the delta over NUM_READINGS + _estimator_total_delta += p_delta; + _estimator_delta_readings++; + + const int NUM_READINGS = 60; + + if (_estimator_delta_readings < NUM_READINGS) { + return; + } + + // use average + p_delta = _estimator_total_delta / NUM_READINGS; + + // reset the averager for next time + _estimator_delta_readings = 0; + _estimator_total_delta = 0; + + /////////////////////////////// + + int fps = Math::round(1000000.0 / p_delta); + + // initial estimation, to speed up converging, special case we will estimate the refresh rate + // from the first average FPS reading + if (_estimated_fps == 0) { + // below 50 might be chugging loading stuff, or else + // dropping loads of frames, so the estimate will be inaccurate + if (fps >= 50) { + _estimated_fps = fps; +#ifdef GODOT_DEBUG_DELTA_SMOOTHER + print_line("initial guess (average measured) refresh rate: " + itos(fps)); +#endif + } else { + // can't get started until above 50 + return; + } + } + + // we hit our exact estimated refresh rate. + // increase our confidence in the estimate. + if (fps == _estimated_fps) { + // note that each hit is an average of NUM_READINGS frames + _hits_at_estimated++; + + if (_estimate_complete && _hits_at_estimated == 20) { + _estimate_locked = true; +#ifdef GODOT_DEBUG_DELTA_SMOOTHER + print_line("estimate LOCKED at " + itos(_estimated_fps) + " fps"); +#endif + return; + } + + // if we are getting pretty confident in this estimate, decide it is complete + // (it can still be increased later, and possibly lowered but only for a short time) + if ((!_estimate_complete) && (_hits_at_estimated > 2)) { + // when the estimate is complete we turn on smoothing + if (_estimated_fps) { + _estimate_complete = true; + _vsync_delta = 1000000 / _estimated_fps; + +#ifdef GODOT_DEBUG_DELTA_SMOOTHER + print_line("estimate complete. vsync_delta " + itos(_vsync_delta) + ", fps " + itos(_estimated_fps)); +#endif + } + } + +#ifdef GODOT_DEBUG_DELTA_SMOOTHER + if ((_hits_at_estimated % (400 / NUM_READINGS)) == 0) { + String sz = "hits at estimated : " + itos(_hits_at_estimated) + ", above : " + itos(_hits_above_estimated) + "( " + itos(_hits_one_above_estimated) + " ), below : " + itos(_hits_below_estimated) + " (" + itos(_hits_one_below_estimated) + " )"; + + print_line(sz); + } +#endif + + return; + } + + const int SIGNIFICANCE_UP = 1; + const int SIGNIFICANCE_DOWN = 2; + + // we are not usually interested in slowing the estimate + // but we may have overshot, so make it possible to reduce + if (fps < _estimated_fps) { + // micro changes + if (fps == (_estimated_fps - 1)) { + _hits_one_below_estimated++; + + if ((_hits_one_below_estimated > _hits_at_estimated) && (_hits_one_below_estimated > SIGNIFICANCE_DOWN)) { + _estimated_fps--; + made_new_estimate(); + } + + return; + } else { + _hits_below_estimated++; + + // don't allow large lowering if we are established at a refresh rate, as it will probably be dropped frames + bool established = _estimate_complete && (_hits_at_estimated > 10); + + // macro changes + // note there is a large barrier to macro lowering. That is because it is more likely to be dropped frames + // than mis-estimation of the refresh rate. + if (!established) { + if (((_hits_below_estimated / 8) > _hits_at_estimated) && (_hits_below_estimated > SIGNIFICANCE_DOWN)) { + // decrease the estimate + _estimated_fps--; + made_new_estimate(); + } + } + + return; + } + } + + // Changes increasing the estimate. + // micro changes + if (fps == (_estimated_fps + 1)) { + _hits_one_above_estimated++; + + if ((_hits_one_above_estimated > _hits_at_estimated) && (_hits_one_above_estimated > SIGNIFICANCE_UP)) { + _estimated_fps++; + made_new_estimate(); + } + return; + } else { + _hits_above_estimated++; + + // macro changes + if ((_hits_above_estimated > _hits_at_estimated) && (_hits_above_estimated > SIGNIFICANCE_UP)) { + // increase the estimate + int change = fps - _estimated_fps; + change /= 2; + change = MAX(1, change); + + _estimated_fps += change; + made_new_estimate(); + } + return; + } +} + +bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int64_t p_delta) { + _measurement_time += p_delta; + _measurement_frame_count++; + + if (_measurement_frame_count == _measurement_end_frame) { + // only switch on or off if the estimate is complete + if (_estimate_complete) { + int64_t time_passed = _measurement_time - _measurement_start_time; + + // average delta + time_passed /= MEASURE_FPS_OVER_NUM_FRAMES; + + // estimate fps + if (time_passed) { + double fps = 1000000.0 / time_passed; + double ratio = fps / (double)_estimated_fps; + + //print_line("ratio : " + String(Variant(ratio))); + + if ((ratio > 0.95) && (ratio < 1.05)) { + _measurement_allows_smoothing = true; + } else { + _measurement_allows_smoothing = false; + } + } + } // estimate complete + + // new start time for next iteration + _measurement_start_time = _measurement_time; + _measurement_end_frame += MEASURE_FPS_OVER_NUM_FRAMES; + } + + return _measurement_allows_smoothing; +} + +int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) { + // Conditions to disable smoothing. + // Note that vsync is a request, it cannot be relied on, the OS may override this. + // If the OS turns vsync on without vsync in the app, smoothing will not be enabled. + // If the OS turns vsync off with sync enabled in the app, the smoothing must detect this + // via the error metric and switch off. + // Also only try smoothing if vsync is enabled (classical vsync, not new types) .. + // This condition is currently checked before calling smooth_delta(). + if (!OS::get_singleton()->is_delta_smoothing_enabled() || Engine::get_singleton()->is_editor_hint()) { + return p_delta; + } + + // only attempt smoothing if vsync is selected + DisplayServer::VSyncMode vsync_mode = DisplayServer::get_singleton()->window_get_vsync_mode(DisplayServer::MAIN_WINDOW_ID); + if (vsync_mode != DisplayServer::VSYNC_ENABLED) { + return p_delta; + } + + // Very important, ignore long deltas and pass them back unmodified. + // This is to deal with resuming after suspend for long periods. + if (p_delta > 1000000) { + return p_delta; + } + + // keep a running guesstimate of the FPS, and turn off smoothing if + // conditions not close to the estimated FPS + if (!fps_allows_smoothing(p_delta)) { + return p_delta; + } + + // we can't cope with negative deltas .. OS bug on some hardware + // and also very small deltas caused by vsync being off. + // This could possibly be part of a hiccup, this value isn't fixed in stone... + if (p_delta < 1000) { + return p_delta; + } + + // note still some vsync off will still get through to this point... + // and we need to cope with it by not converging the estimator / and / or not smoothing + update_refresh_rate_estimator(p_delta); + + // no smoothing until we know what the refresh rate is + if (!_estimate_complete) { + return p_delta; + } + + // accumulate the time we have available to use + _leftover_time += p_delta; + + // how many vsyncs units can we fit? + int64_t units = _leftover_time / _vsync_delta; + + // a delta must include minimum 1 vsync + // (if it is less than that, it is either random error or we are no longer running at the vsync rate, + // in which case we should switch off delta smoothing, or re-estimate the refresh rate) + units = MAX(units, 1); + + _leftover_time -= units * _vsync_delta; + // print_line("units " + itos(units) + ", leftover " + itos(_leftover_time/1000) + " ms"); + + return units * _vsync_delta; +} + +///////////////////////////////////// + // returns the fraction of p_physics_step required for the timer to overshoot // before advance_core considers changing the physics_steps return from // the typical values as defined by typical_physics_steps @@ -236,6 +491,8 @@ double MainTimerSync::get_cpu_process_step() { uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec; last_cpu_ticks_usec = current_cpu_ticks_usec; + cpu_ticks_elapsed = _delta_smoother.smooth_delta(cpu_ticks_elapsed); + return cpu_ticks_elapsed / 1000000.0; } diff --git a/main/main_timer_sync.h b/main/main_timer_sync.h index 47f05aba58..d8b5d4a02d 100644 --- a/main/main_timer_sync.h +++ b/main/main_timer_sync.h @@ -33,6 +33,9 @@ #include "core/config/engine.h" +// Uncomment this define to get more debugging logs for the delta smoothing. +// #define GODOT_DEBUG_DELTA_SMOOTHER + struct MainFrameTime { double process_step; // delta time to advance during process() int physics_steps; // number of times to iterate the physics engine @@ -42,6 +45,66 @@ struct MainFrameTime { }; class MainTimerSync { + class DeltaSmoother { + public: + // pass the recorded delta, returns a smoothed delta + int64_t smooth_delta(int64_t p_delta); + + private: + void update_refresh_rate_estimator(int64_t p_delta); + bool fps_allows_smoothing(int64_t p_delta); + + // estimated vsync delta (monitor refresh rate) + int64_t _vsync_delta = 16666; + + // keep track of accumulated time so we know how many vsyncs to advance by + int64_t _leftover_time = 0; + + // keep a rough measurement of the FPS as we run. + // If this drifts a long way below or above the refresh rate, the machine + // is struggling to keep up, and we can switch off smoothing. This + // also deals with the case that the user has overridden the vsync in the GPU settings, + // in which case we don't want to try smoothing. + static const int MEASURE_FPS_OVER_NUM_FRAMES = 64; + + int64_t _measurement_time = 0; + int64_t _measurement_frame_count = 0; + int64_t _measurement_end_frame = MEASURE_FPS_OVER_NUM_FRAMES; + int64_t _measurement_start_time = 0; + bool _measurement_allows_smoothing = true; + + // we can estimate the fps by growing it on condition + // that a large proportion of frames are higher than the current estimate. + int32_t _estimated_fps = 0; + int32_t _hits_at_estimated = 0; + int32_t _hits_above_estimated = 0; + int32_t _hits_below_estimated = 0; + int32_t _hits_one_above_estimated = 0; + int32_t _hits_one_below_estimated = 0; + bool _estimate_complete = false; + bool _estimate_locked = false; + + // data for averaging the delta over a second or so + // to prevent spurious values + int64_t _estimator_total_delta = 0; + int32_t _estimator_delta_readings = 0; + + void made_new_estimate() { + _hits_above_estimated = 0; + _hits_at_estimated = 0; + _hits_below_estimated = 0; + _hits_one_above_estimated = 0; + _hits_one_below_estimated = 0; + + _estimate_complete = false; + +#ifdef GODOT_DEBUG_DELTA_SMOOTHER + print_line("estimated fps " + itos(_estimated_fps)); +#endif + } + + } _delta_smoother; + // wall clock time measured on the main thread uint64_t last_cpu_ticks_usec = 0; uint64_t current_cpu_ticks_usec = 0; diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index c8fe39dab0..c241f1cabd 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -1827,7 +1827,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { } } - if (!path || !path->is_inside_tree()) { + if (!path) { return new_brush; } diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 556f747eb3..f383eed480 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -5,7 +5,8 @@ </brief_description> <description> A script implemented in the GDScript programming language. The script extends the functionality of all objects that instantiate it. - [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. + Calling [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. + If you are looking for GDScript's built-in functions, see [@GDScript] instead. </description> <tutorials> <link title="GDScript documentation index">$DOCS_URL/tutorials/scripting/gdscript/index.html</link> diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index 63045237e9..37a019eaa4 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -138,22 +138,19 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod } void AndroidInputHandler::_cancel_all_touch() { - _parse_all_touch(false, false, true); + _parse_all_touch(false, true); touch.clear(); } -void AndroidInputHandler::_parse_all_touch(bool p_pressed, bool p_double_tap, bool reset_index) { +void AndroidInputHandler::_parse_all_touch(bool p_pressed, bool p_canceled, bool p_double_tap) { if (touch.size()) { //end all if exist for (int i = 0; i < touch.size(); i++) { Ref<InputEventScreenTouch> ev; ev.instantiate(); - if (reset_index) { - ev->set_index(-1); - } else { - ev->set_index(touch[i].id); - } + ev->set_index(touch[i].id); ev->set_pressed(p_pressed); + ev->set_canceled(p_canceled); ev->set_position(touch[i].pos); ev->set_double_tap(p_double_tap); Input::get_singleton()->parse_input_event(ev); @@ -180,7 +177,7 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const } //send touch - _parse_all_touch(true, p_double_tap); + _parse_all_touch(true, false, p_double_tap); } break; case AMOTION_EVENT_ACTION_MOVE: { //motion @@ -257,11 +254,11 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const void AndroidInputHandler::_cancel_mouse_event_info(bool p_source_mouse_relative) { buttons_state = BitField<MouseButtonMask>(); - _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, p_source_mouse_relative); + _parse_mouse_event_info(BitField<MouseButtonMask>(), false, true, false, p_source_mouse_relative); mouse_event_info.valid = false; } -void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_double_click, bool p_source_mouse_relative) { +void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_canceled, bool p_double_click, bool p_source_mouse_relative) { if (!mouse_event_info.valid) { return; } @@ -278,6 +275,7 @@ void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> even hover_prev_pos = mouse_event_info.pos; } ev->set_pressed(p_pressed); + ev->set_canceled(p_canceled); BitField<MouseButtonMask> changed_button_mask = BitField<MouseButtonMask>(buttons_state.operator int64_t() ^ event_buttons_mask.operator int64_t()); buttons_state = event_buttons_mask; @@ -289,7 +287,7 @@ void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> even } void AndroidInputHandler::_release_mouse_event_info(bool p_source_mouse_relative) { - _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, p_source_mouse_relative); + _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, false, p_source_mouse_relative); mouse_event_info.valid = false; } @@ -318,7 +316,7 @@ void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_an mouse_event_info.valid = true; mouse_event_info.pos = p_event_pos; - _parse_mouse_event_info(event_buttons_mask, true, p_double_click, p_source_mouse_relative); + _parse_mouse_event_info(event_buttons_mask, true, false, p_double_click, p_source_mouse_relative); } break; case AMOTION_EVENT_ACTION_CANCEL: { diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h index 2badd32636..42d1c228a8 100644 --- a/platform/android/android_input_handler.h +++ b/platform/android/android_input_handler.h @@ -83,13 +83,13 @@ private: void _wheel_button_click(BitField<MouseButtonMask> event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor); - void _parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_double_click, bool p_source_mouse_relative); + void _parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_canceled, bool p_double_click, bool p_source_mouse_relative); void _release_mouse_event_info(bool p_source_mouse_relative = false); void _cancel_mouse_event_info(bool p_source_mouse_relative = false); - void _parse_all_touch(bool p_pressed, bool p_double_tap, bool reset_index = false); + void _parse_all_touch(bool p_pressed, bool p_canceled = false, bool p_double_tap = false); void _release_all_touch(); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 24478fd847..a0eab67f28 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -112,12 +112,24 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { } #endif -void Node2D::_update_xform_values() { +void Node2D::_set_xform_dirty(bool p_dirty) const { + if (is_group_processing()) { + if (p_dirty) { + xform_dirty.mt.set(); + } else { + xform_dirty.mt.clear(); + } + } else { + xform_dirty.st = p_dirty; + } +} + +void Node2D::_update_xform_values() const { rotation = transform.get_rotation(); skew = transform.get_skew(); position = transform.columns[2]; scale = transform.get_scale(); - xform_dirty.clear(); + _set_xform_dirty(false); } void Node2D::_update_transform() { @@ -144,8 +156,8 @@ void Node2D::reparent(Node *p_parent, bool p_keep_global_transform) { void Node2D::set_position(const Point2 &p_pos) { ERR_THREAD_GUARD; - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } position = p_pos; _update_transform(); @@ -153,8 +165,8 @@ void Node2D::set_position(const Point2 &p_pos) { void Node2D::set_rotation(real_t p_radians) { ERR_THREAD_GUARD; - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } rotation = p_radians; _update_transform(); @@ -167,8 +179,8 @@ void Node2D::set_rotation_degrees(real_t p_degrees) { void Node2D::set_skew(real_t p_radians) { ERR_THREAD_GUARD; - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } skew = p_radians; _update_transform(); @@ -176,8 +188,8 @@ void Node2D::set_skew(real_t p_radians) { void Node2D::set_scale(const Size2 &p_scale) { ERR_THREAD_GUARD; - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } scale = p_scale; // Avoid having 0 scale values, can lead to errors in physics and rendering. @@ -192,8 +204,8 @@ void Node2D::set_scale(const Size2 &p_scale) { Point2 Node2D::get_position() const { ERR_READ_THREAD_GUARD_V(Point2()); - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } return position; @@ -201,8 +213,8 @@ Point2 Node2D::get_position() const { real_t Node2D::get_rotation() const { ERR_READ_THREAD_GUARD_V(0); - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } return rotation; @@ -215,8 +227,8 @@ real_t Node2D::get_rotation_degrees() const { real_t Node2D::get_skew() const { ERR_READ_THREAD_GUARD_V(0); - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } return skew; @@ -224,8 +236,8 @@ real_t Node2D::get_skew() const { Size2 Node2D::get_scale() const { ERR_READ_THREAD_GUARD_V(Size2()); - if (xform_dirty.is_set()) { - const_cast<Node2D *>(this)->_update_xform_values(); + if (_is_xform_dirty()) { + _update_xform_values(); } return scale; @@ -362,7 +374,7 @@ void Node2D::set_global_scale(const Size2 &p_scale) { void Node2D::set_transform(const Transform2D &p_transform) { ERR_THREAD_GUARD; transform = p_transform; - xform_dirty.set(); + _set_xform_dirty(true); RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index 6bdf5d6eb5..de46dbd7d6 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -36,17 +36,20 @@ class Node2D : public CanvasItem { GDCLASS(Node2D, CanvasItem); - SafeFlag xform_dirty; - Point2 position; - real_t rotation = 0.0; - Size2 scale = Vector2(1, 1); - real_t skew = 0.0; + mutable MTFlag xform_dirty; + mutable Point2 position; + mutable real_t rotation = 0.0; + mutable Size2 scale = Vector2(1, 1); + mutable real_t skew = 0.0; Transform2D transform; + _FORCE_INLINE_ bool _is_xform_dirty() const { return is_group_processing() ? xform_dirty.mt.is_set() : xform_dirty.st; } + void _set_xform_dirty(bool p_dirty) const; + void _update_transform(); - void _update_xform_values(); + void _update_xform_values() const; protected: void _notification(int p_notification); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 446d9f6ee8..80289bac52 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -87,7 +87,7 @@ void Node3D::_notify_dirty() { void Node3D::_update_local_transform() const { // This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale. data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order); - data.dirty.bit_and(~DIRTY_LOCAL_TRANSFORM); + _clear_dirty_bits(DIRTY_LOCAL_TRANSFORM); } void Node3D::_update_rotation_and_scale() const { @@ -95,7 +95,7 @@ void Node3D::_update_rotation_and_scale() const { data.scale = data.local_transform.basis.get_scale(); data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); - data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE); + _clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE); } void Node3D::_propagate_transform_changed_deferred() { @@ -127,7 +127,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_propagate_transform_changed_deferred)); } } - data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM); + _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); } void Node3D::_notification(int p_what) { @@ -151,12 +151,12 @@ void Node3D::_notification(int p_what) { if (data.top_level && !Engine::get_singleton()->is_editor_hint()) { if (data.parent) { data.local_transform = data.parent->get_global_transform() * get_transform(); - data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty. + _replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty. } data.top_level_active = true; } - data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene. + _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene. _notify_dirty(); notification(NOTIFICATION_ENTER_WORLD); @@ -230,16 +230,16 @@ void Node3D::set_basis(const Basis &p_basis) { void Node3D::set_quaternion(const Quaternion &p_quaternion) { ERR_THREAD_GUARD; - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // We need the scale part, so if these are dirty, update it data.scale = data.local_transform.basis.get_scale(); - data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE); + _clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE); } data.local_transform.basis = Basis(p_quaternion, data.scale); // Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now. data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); - data.dirty.set(DIRTY_NONE); + _replace_dirty_mask(DIRTY_NONE); _propagate_transform_changed(this); if (data.notify_local_transform) { @@ -286,7 +286,7 @@ void Node3D::set_global_rotation_degrees(const Vector3 &p_euler_degrees) { void Node3D::set_transform(const Transform3D &p_transform) { ERR_THREAD_GUARD; data.local_transform = p_transform; - data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty. + _replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty. _propagate_transform_changed(this); if (data.notify_local_transform) { @@ -314,7 +314,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) { Transform3D Node3D::get_transform() const { ERR_READ_THREAD_GUARD_V(Transform3D()); - if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) { + if (_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) { // This update can happen if needed over multiple threads. _update_local_transform(); } @@ -330,7 +330,7 @@ Transform3D Node3D::get_global_transform() const { * the dirty/update process is thread safe by utilizing atomic copies. */ - uint32_t dirty = data.dirty.get(); + uint32_t dirty = _read_dirty_mask(); if (dirty & DIRTY_GLOBAL_TRANSFORM) { if (dirty & DIRTY_LOCAL_TRANSFORM) { _update_local_transform(); // Update local transform atomically. @@ -348,7 +348,7 @@ Transform3D Node3D::get_global_transform() const { } data.global_transform = new_global; - data.dirty.bit_and(~DIRTY_GLOBAL_TRANSFORM); + _clear_dirty_bits(DIRTY_GLOBAL_TRANSFORM); } return data.global_transform; @@ -404,14 +404,14 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) { } bool transform_changed = false; - if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty.get() & DIRTY_LOCAL_TRANSFORM)) { + if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) { data.local_transform.orthogonalize(); transform_changed = true; } data.rotation_edit_mode = p_mode; - if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE)) { + if (p_mode == ROTATION_EDIT_MODE_EULER && _test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong. // Otherwise keep what is there, so switching back and forth between modes does not break the vectors. @@ -442,13 +442,14 @@ void Node3D::set_rotation_order(EulerOrder p_order) { ERR_FAIL_INDEX(int32_t(p_order), 6); bool transform_changed = false; - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + uint32_t dirty = _read_dirty_mask(); + if ((dirty & DIRTY_EULER_ROTATION_AND_SCALE)) { _update_rotation_and_scale(); - } else if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) { + } else if ((dirty & DIRTY_LOCAL_TRANSFORM)) { data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(p_order); transform_changed = true; } else { - data.dirty.bit_or(DIRTY_LOCAL_TRANSFORM); + _set_dirty_bits(DIRTY_LOCAL_TRANSFORM); transform_changed = true; } @@ -470,14 +471,14 @@ EulerOrder Node3D::get_rotation_order() const { void Node3D::set_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // Update scale only if rotation and scale are dirty, as rotation will be overridden. data.scale = data.local_transform.basis.get_scale(); - data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE); + _clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE); } data.euler_rotation = p_euler_rad; - data.dirty.set(DIRTY_LOCAL_TRANSFORM); + _replace_dirty_mask(DIRTY_LOCAL_TRANSFORM); _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -492,14 +493,14 @@ void Node3D::set_rotation_degrees(const Vector3 &p_euler_degrees) { void Node3D::set_scale(const Vector3 &p_scale) { ERR_THREAD_GUARD; - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // Update rotation only if rotation and scale are dirty, as scale will be overridden. data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); - data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE); + _clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE); } data.scale = p_scale; - data.dirty.set(DIRTY_LOCAL_TRANSFORM); + _replace_dirty_mask(DIRTY_LOCAL_TRANSFORM); _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -513,7 +514,7 @@ Vector3 Node3D::get_position() const { Vector3 Node3D::get_rotation() const { ERR_READ_THREAD_GUARD_V(Vector3()); - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { _update_rotation_and_scale(); } @@ -528,7 +529,7 @@ Vector3 Node3D::get_rotation_degrees() const { Vector3 Node3D::get_scale() const { ERR_READ_THREAD_GUARD_V(Vector3()); - if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) { + if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { _update_rotation_and_scale(); } @@ -645,6 +646,30 @@ Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const { #endif } +void Node3D::_replace_dirty_mask(uint32_t p_mask) const { + if (is_group_processing()) { + data.dirty.mt.set(p_mask); + } else { + data.dirty.st = p_mask; + } +} + +void Node3D::_set_dirty_bits(uint32_t p_bits) const { + if (is_group_processing()) { + data.dirty.mt.bit_or(p_bits); + } else { + data.dirty.st |= p_bits; + } +} + +void Node3D::_clear_dirty_bits(uint32_t p_bits) const { + if (is_group_processing()) { + data.dirty.mt.bit_and(~p_bits); + } else { + data.dirty.st &= ~p_bits; + } +} + void Node3D::_update_gizmos() { #ifdef TOOLS_ENABLED if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) { diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 4fb77eeb9f..b274a6af88 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -97,7 +97,7 @@ private: mutable Vector3 scale = Vector3(1, 1, 1); mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER; - mutable SafeNumeric<uint32_t> dirty; + mutable MTNumeric<uint32_t> dirty; Viewport *viewport = nullptr; @@ -129,6 +129,12 @@ private: NodePath visibility_parent_path; + _FORCE_INLINE_ uint32_t _read_dirty_mask() const { return is_group_processing() ? data.dirty.mt.get() : data.dirty.st; } + _FORCE_INLINE_ bool _test_dirty_bits(uint32_t p_bits) const { return is_group_processing() ? data.dirty.mt.bit_and(p_bits) : (data.dirty.st & p_bits); } + void _replace_dirty_mask(uint32_t p_mask) const; + void _set_dirty_bits(uint32_t p_bits) const; + void _clear_dirty_bits(uint32_t p_bits) const; + void _update_gizmos(); void _notify_dirty(); void _propagate_transform_changed(Node3D *p_origin); diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h index a5af7dea54..1b9c378b4f 100644 --- a/scene/animation/easing_equations.h +++ b/scene/animation/easing_equations.h @@ -78,7 +78,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace sine @@ -104,7 +105,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace quint @@ -130,7 +132,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace quart @@ -157,7 +160,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace quad @@ -197,7 +201,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace expo @@ -264,7 +269,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace elastic @@ -293,7 +299,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace cubic @@ -322,7 +329,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace circ @@ -356,14 +364,16 @@ static real_t in_out(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return in(t * 2, b, c / 2, d); } - return out(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return out(t * 2 - d, b + h, h, d); } static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace bounce @@ -398,7 +408,8 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { if (t < d / 2) { return out(t * 2, b, c / 2, d); } - return in(t * 2 - d, b + c / 2, c / 2, d); + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); } }; // namespace back diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 46ac8187c4..e34384dd6c 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -46,6 +46,9 @@ void Button::_set_internal_margin(Side p_side, float p_value) { _internal_margin[p_side] = p_value; } +void Button::_queue_update_size_cache() { +} + void Button::_update_theme_item_cache() { BaseButton::_update_theme_item_cache(); @@ -544,6 +547,7 @@ Ref<Texture2D> Button::get_icon() const { void Button::set_expand_icon(bool p_enabled) { if (expand_icon != p_enabled) { expand_icon = p_enabled; + _queue_update_size_cache(); queue_redraw(); update_minimum_size(); } diff --git a/scene/gui/button.h b/scene/gui/button.h index 733f40c84e..792e7e24da 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -100,6 +100,7 @@ private: protected: void _set_internal_margin(Side p_side, float p_value); virtual void _update_theme_item_cache() override; + virtual void _queue_update_size_cache(); void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 750c631faa..451ac94109 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -176,7 +176,7 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { } if (property == "text" || property == "icon") { - _queue_refresh_cache(); + _queue_update_size_cache(); } return valid; @@ -243,7 +243,7 @@ void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_l if (first_selectable) { select(get_item_count() - 1); } - _queue_refresh_cache(); + _queue_update_size_cache(); } void OptionButton::add_item(const String &p_label, int p_id) { @@ -252,7 +252,7 @@ void OptionButton::add_item(const String &p_label, int p_id) { if (first_selectable) { select(get_item_count() - 1); } - _queue_refresh_cache(); + _queue_update_size_cache(); } void OptionButton::set_item_text(int p_idx, const String &p_text) { @@ -261,7 +261,7 @@ void OptionButton::set_item_text(int p_idx, const String &p_text) { if (current == p_idx) { set_text(p_text); } - _queue_refresh_cache(); + _queue_update_size_cache(); } void OptionButton::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { @@ -270,7 +270,7 @@ void OptionButton::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { if (current == p_idx) { set_icon(p_icon); } - _queue_refresh_cache(); + _queue_update_size_cache(); } void OptionButton::set_item_id(int p_idx, int p_id) { @@ -456,7 +456,7 @@ void OptionButton::_refresh_size_cache() { update_minimum_size(); } -void OptionButton::_queue_refresh_cache() { +void OptionButton::_queue_update_size_cache() { if (cache_refresh_pending) { return; } @@ -490,7 +490,7 @@ void OptionButton::remove_item(int p_idx) { if (current == p_idx) { _select(NONE_SELECTED); } - _queue_refresh_cache(); + _queue_update_size_cache(); } PopupMenu *OptionButton::get_popup() const { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index b741ebeedd..7dcb3319c6 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -66,13 +66,13 @@ class OptionButton : public Button { void _select(int p_which, bool p_emit = false); void _select_int(int p_which); void _refresh_size_cache(); - void _queue_refresh_cache(); virtual void pressed() override; protected: Size2 get_minimum_size() const override; virtual void _update_theme_item_cache() override; + virtual void _queue_update_size_cache() override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 598f6aa4c6..ae01e8b009 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -148,7 +148,7 @@ void CanvasItem::_redraw_callback() { } void CanvasItem::_invalidate_global_transform() { - global_invalid.set(); + _set_global_invalid(true); } Transform2D CanvasItem::get_global_transform_with_canvas() const { @@ -171,7 +171,7 @@ Transform2D CanvasItem::get_screen_transform() const { Transform2D CanvasItem::get_global_transform() const { ERR_READ_THREAD_GUARD_V(Transform2D()); - if (global_invalid.is_set()) { + if (_is_global_invalid()) { // This code can enter multiple times from threads if dirty, this is expected. const CanvasItem *pi = get_parent_item(); Transform2D new_global; @@ -182,12 +182,24 @@ Transform2D CanvasItem::get_global_transform() const { } global_transform = new_global; - global_invalid.clear(); + _set_global_invalid(false); } return global_transform; } +void CanvasItem::_set_global_invalid(bool p_invalid) const { + if (is_group_processing()) { + if (p_invalid) { + global_invalid.mt.set(); + } else { + global_invalid.mt.clear(); + } + } else { + global_invalid.st = p_invalid; + } +} + void CanvasItem::_top_level_raise_self() { if (!is_inside_tree()) { return; @@ -308,7 +320,7 @@ void CanvasItem::_notification(int p_what) { } } - global_invalid.set(); + _set_global_invalid(true); _enter_canvas(); RenderingServer::get_singleton()->canvas_item_set_visible(canvas_item, is_visible_in_tree()); // The visibility of the parent may change. @@ -341,7 +353,7 @@ void CanvasItem::_notification(int p_what) { window->disconnect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); window = nullptr; } - global_invalid.set(); + _set_global_invalid(true); parent_visible_in_tree = false; if (get_viewport()) { @@ -869,11 +881,11 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) { * notification anyway). */ - if (/*p_node->xform_change.in_list() &&*/ p_node->global_invalid.is_set()) { + if (/*p_node->xform_change.in_list() &&*/ p_node->_is_global_invalid()) { return; //nothing to do } - p_node->global_invalid.set(); + p_node->_set_global_invalid(true); if (p_node->notify_transform && !p_node->xform_change.in_list()) { if (!p_node->block_transform_notify) { diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 55267abab8..d7771cda53 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -118,7 +118,10 @@ private: Ref<Material> material; mutable Transform2D global_transform; - mutable SafeFlag global_invalid; + mutable MTFlag global_invalid; + + _FORCE_INLINE_ bool _is_global_invalid() const { return is_group_processing() ? global_invalid.mt.is_set() : global_invalid.st; } + void _set_global_invalid(bool p_invalid) const; void _top_level_raise_self(); diff --git a/scene/main/node.h b/scene/main/node.h index b7462b4468..4d4e71ee56 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -42,9 +42,25 @@ class SceneState; class Tween; class PropertyTweener; +SAFE_FLAG_TYPE_PUN_GUARANTEES +SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t) + class Node : public Object { GDCLASS(Node, Object); +protected: + // During group processing, these are thread-safe. + // Outside group processing, these avoid the cost of sync by working as plain primitive types. + union MTFlag { + SafeFlag mt{}; + bool st; + }; + template <class T> + union MTNumeric { + SafeNumeric<T> mt{}; + T st; + }; + public: enum ProcessMode { PROCESS_MODE_INHERIT, // same as parent node @@ -522,8 +538,8 @@ public: _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(); + // if accessing from the main thread or being loaded. + return !data.inside_tree || Thread::is_main_thread() || ResourceLoader::is_within_load(); } else { // Thread processing return current_process_thread_group == data.process_thread_group_owner; @@ -532,12 +548,14 @@ public: _FORCE_INLINE_ bool is_readable_from_caller_thread() const { if (current_process_thread_group == nullptr) { - return Thread::is_main_thread(); + return Thread::is_main_thread() || ResourceLoader::is_within_load(); } else { return true; } } + _FORCE_INLINE_ static bool is_group_processing() { return current_process_thread_group; } + void set_process_thread_messages(BitField<ProcessThreadMessages> p_flags); BitField<ProcessThreadMessages> get_process_thread_messages() const; diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 6a872da69e..032f2a6275 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -197,8 +197,8 @@ void RendererSceneCull::_instance_pair(Instance *p_A, Instance *p_B) { InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data); if (A->dynamic_gi) { - geom->lightmap_captures.insert(A); - lightmap_data->geometries.insert(B); + geom->lightmap_captures.insert(B); + lightmap_data->geometries.insert(A); if (A->scenario && A->array_index >= 0) { InstanceData &idata = A->scenario->instance_data[A->array_index]; |