summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/core_bind.cpp12
-rw-r--r--core/core_bind.h3
-rw-r--r--core/extension/make_interface_dumper.py35
-rw-r--r--core/input/input.cpp2
-rw-r--r--core/input/input_event.cpp89
-rw-r--r--core/input/input_event.h24
-rw-r--r--core/io/resource_loader.cpp35
-rw-r--r--core/object/ref_counted.h17
-rw-r--r--core/object/worker_thread_pool.cpp208
-rw-r--r--core/object/worker_thread_pool.h11
-rw-r--r--core/os/os.cpp8
-rw-r--r--core/os/os.h4
-rw-r--r--core/templates/safe_refcount.h5
-rw-r--r--doc/classes/InputEvent.xml12
-rw-r--r--doc/classes/InputEventMouseButton.xml3
-rw-r--r--doc/classes/InputEventScreenTouch.xml3
-rw-r--r--doc/classes/OS.xml3
-rw-r--r--doc/classes/ProjectSettings.xml5
-rw-r--r--doc/classes/WorkerThreadPool.xml5
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp3
-rw-r--r--editor/project_converter_3_to_4.cpp1
-rw-r--r--editor/project_manager.cpp10
-rw-r--r--main/main.cpp30
-rw-r--r--main/main_timer_sync.cpp257
-rw-r--r--main/main_timer_sync.h63
-rw-r--r--modules/csg/csg_shape.cpp2
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml3
-rw-r--r--platform/android/android_input_handler.cpp22
-rw-r--r--platform/android/android_input_handler.h4
-rw-r--r--scene/2d/node_2d.cpp50
-rw-r--r--scene/2d/node_2d.h15
-rw-r--r--scene/3d/node_3d.cpp75
-rw-r--r--scene/3d/node_3d.h8
-rw-r--r--scene/animation/easing_equations.h33
-rw-r--r--scene/gui/button.cpp4
-rw-r--r--scene/gui/button.h1
-rw-r--r--scene/gui/option_button.cpp14
-rw-r--r--scene/gui/option_button.h2
-rw-r--r--scene/main/canvas_item.cpp26
-rw-r--r--scene/main/canvas_item.h5
-rw-r--r--scene/main/node.h24
-rw-r--r--servers/rendering/renderer_scene_cull.cpp4
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="&quot;&quot;">
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];