summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/project_settings.cpp21
-rw-r--r--core/core_bind.cpp29
-rw-r--r--core/core_bind.h17
-rw-r--r--core/core_constants.cpp2
-rw-r--r--core/core_constants.h2
-rw-r--r--core/debugger/engine_debugger.cpp2
-rw-r--r--core/debugger/remote_debugger.cpp33
-rw-r--r--core/debugger/remote_debugger.h1
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension.h4
-rw-r--r--core/input/input.cpp3
-rw-r--r--core/input/input_builders.py4
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/io/file_access_pack.cpp2
-rw-r--r--core/io/http_client.cpp2
-rw-r--r--core/io/image.cpp1
-rw-r--r--core/io/image.h5
-rw-r--r--core/io/plist.cpp868
-rw-r--r--core/io/plist.h128
-rw-r--r--core/io/resource.cpp118
-rw-r--r--core/io/resource.h7
-rw-r--r--core/io/resource_loader.cpp27
-rw-r--r--core/io/resource_saver.cpp5
-rw-r--r--core/math/a_star.cpp56
-rw-r--r--core/math/a_star_grid_2d.cpp39
-rw-r--r--core/math/a_star_grid_2d.h12
-rw-r--r--core/math/aabb.h6
-rw-r--r--core/math/basis.h14
-rw-r--r--core/math/geometry_2d.h26
-rw-r--r--core/math/geometry_3d.h16
-rw-r--r--core/math/math_funcs.h16
-rw-r--r--core/math/transform_2d.cpp12
-rw-r--r--core/math/transform_2d.h2
-rw-r--r--core/math/transform_3d.cpp11
-rw-r--r--core/math/transform_3d.h2
-rw-r--r--core/math/vector2i.h8
-rw-r--r--core/math/vector3i.h11
-rw-r--r--core/math/vector4i.h11
-rw-r--r--core/object/class_db.cpp7
-rw-r--r--core/object/object.cpp2
-rw-r--r--core/object/object.h2
-rw-r--r--core/object/script_language.h3
-rw-r--r--core/object/script_language_extension.cpp2
-rw-r--r--core/object/script_language_extension.h3
-rw-r--r--core/object/worker_thread_pool.cpp528
-rw-r--r--core/object/worker_thread_pool.h64
-rw-r--r--core/os/condition_variable.h16
-rw-r--r--core/os/keyboard.cpp4
-rw-r--r--core/os/mutex.cpp4
-rw-r--r--core/os/mutex.h40
-rw-r--r--core/os/os.cpp6
-rw-r--r--core/os/semaphore.h25
-rw-r--r--core/os/thread.cpp9
-rw-r--r--core/os/thread.h64
-rw-r--r--core/register_core_types.cpp1
-rw-r--r--core/string/translation.cpp10
-rw-r--r--core/string/ustring.cpp59
-rw-r--r--core/string/ustring.h8
-rw-r--r--core/templates/command_queue_mt.h50
-rw-r--r--core/templates/paged_allocator.h6
-rw-r--r--core/templates/paged_array.h21
-rw-r--r--core/templates/self_list.h6
-rw-r--r--core/typedefs.h4
-rw-r--r--core/variant/callable.cpp3
-rw-r--r--core/variant/variant.h22
-rw-r--r--core/variant/variant_call.cpp101
-rw-r--r--core/variant/variant_callable.cpp81
-rw-r--r--core/variant/variant_callable.h58
-rw-r--r--core/variant/variant_op.cpp9
-rw-r--r--core/variant/variant_setget.cpp93
70 files changed, 2304 insertions, 544 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 56e041be1b..90e2e27320 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1393,8 +1393,8 @@ ProjectSettings::ProjectSettings() {
// - Have a 16:9 aspect ratio,
// - Have both dimensions divisible by 8 to better play along with video recording,
// - Be displayable correctly in windowed mode on a 1366×768 display (tested on Windows 10 with default settings).
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_width", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 1152); // 8K resolution
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_height", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 648); // 8K resolution
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_width", PROPERTY_HINT_RANGE, "1,7680,1,or_greater"), 1152); // 8K resolution
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_height", PROPERTY_HINT_RANGE, "1,4320,1,or_greater"), 648); // 8K resolution
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen,Exclusive Fullscreen"), 0);
@@ -1410,8 +1410,8 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/extend_to_title", false);
GLOBAL_DEF("display/window/size/no_focus", false);
- GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution
- GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "1,7680,1,or_greater"), 0); // 8K resolution
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "1,4320,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);
@@ -1462,7 +1462,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("debug/settings/crash_handler/message.editor",
String("Please include this when reporting the bug on: https://github.com/godotengine/godot/issues"));
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2);
- GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes.
GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), 0);
@@ -1471,11 +1470,11 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true);
GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true);
- GLOBAL_DEF("rendering/rendering_device/staging_buffer/block_size_kb", 256);
- GLOBAL_DEF("rendering/rendering_device/staging_buffer/max_size_mb", 128);
- GLOBAL_DEF("rendering/rendering_device/staging_buffer/texture_upload_region_size_px", 64);
- GLOBAL_DEF("rendering/rendering_device/pipeline_cache/save_chunk_size_mb", 3.0);
- GLOBAL_DEF("rendering/rendering_device/vulkan/max_descriptors_per_pool", 64);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/block_size_kb", PROPERTY_HINT_RANGE, "4,2048,1,or_greater"), 256);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/max_size_mb", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 128);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/texture_upload_region_size_px", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/rendering_device/pipeline_cache/save_chunk_size_mb", PROPERTY_HINT_RANGE, "0.000001,64.0,0.001,or_greater"), 3.0);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/vulkan/max_descriptors_per_pool", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_resource_descriptors_per_frame", 16384);
custom_prop_info["rendering/rendering_device/d3d12/max_resource_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_resource_descriptors_per_frame", PROPERTY_HINT_RANGE, "512,262144");
@@ -1484,6 +1483,8 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", 512);
custom_prop_info["rendering/rendering_device/d3d12/max_misc_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", PROPERTY_HINT_RANGE, "32,4096");
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version"), 610);
+
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror"), 0);
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index d91c659d1e..e5363f9acc 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -1040,6 +1040,10 @@ Vector<Vector3> Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const
return ::Geometry3D::clip_polygon(p_points, p_plane);
}
+Vector<int32_t> Geometry3D::tetrahedralize_delaunay(const Vector<Vector3> &p_points) {
+ return ::Geometry3D::tetrahedralize_delaunay(p_points);
+}
+
void Geometry3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("compute_convex_mesh_points", "planes"), &Geometry3D::compute_convex_mesh_points);
ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &Geometry3D::build_box_planes);
@@ -1061,6 +1065,7 @@ void Geometry3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("segment_intersects_convex", "from", "to", "planes"), &Geometry3D::segment_intersects_convex);
ClassDB::bind_method(D_METHOD("clip_polygon", "points", "plane"), &Geometry3D::clip_polygon);
+ ClassDB::bind_method(D_METHOD("tetrahedralize_delaunay", "points"), &Geometry3D::tetrahedralize_delaunay);
}
////// Marshalls //////
@@ -1370,11 +1375,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const {
}
}
-bool ClassDB::class_has_signal(StringName p_class, StringName p_signal) const {
+bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const {
return ::ClassDB::has_signal(p_class, p_signal);
}
-Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) const {
+Dictionary ClassDB::class_get_signal(const StringName &p_class, const StringName &p_signal) const {
MethodInfo signal;
if (::ClassDB::get_signal(p_class, p_signal, &signal)) {
return signal.operator Dictionary();
@@ -1383,7 +1388,7 @@ Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) co
}
}
-TypedArray<Dictionary> ClassDB::class_get_signal_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_signal_list(const StringName &p_class, bool p_no_inheritance) const {
List<MethodInfo> signals;
::ClassDB::get_signal_list(p_class, &signals, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1395,7 +1400,7 @@ TypedArray<Dictionary> ClassDB::class_get_signal_list(StringName p_class, bool p
return ret;
}
-TypedArray<Dictionary> ClassDB::class_get_property_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_property_list(const StringName &p_class, bool p_no_inheritance) const {
List<PropertyInfo> plist;
::ClassDB::get_property_list(p_class, &plist, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1423,11 +1428,11 @@ Error ClassDB::class_set_property(Object *p_object, const StringName &p_property
return OK;
}
-bool ClassDB::class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const {
+bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const {
return ::ClassDB::has_method(p_class, p_method, p_no_inheritance);
}
-TypedArray<Dictionary> ClassDB::class_get_method_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class, bool p_no_inheritance) const {
List<MethodInfo> methods;
::ClassDB::get_method_list(p_class, &methods, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1508,7 +1513,7 @@ StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, c
return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance);
}
-bool ClassDB::is_class_enabled(StringName p_class) const {
+bool ClassDB::is_class_enabled(const StringName &p_class) const {
return ::ClassDB::is_class_enabled(p_class);
}
@@ -1718,6 +1723,16 @@ bool Engine::is_printing_error_messages() const {
return ::Engine::get_singleton()->is_printing_error_messages();
}
+void Engine::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+ String pf = p_function;
+ if (p_idx == 0 && (pf == "has_singleton" || pf == "get_singleton" || pf == "unregister_singleton")) {
+ for (const String &E : get_singleton_list()) {
+ r_options->push_back(E.quote());
+ }
+ }
+ Object::get_argument_options(p_function, p_idx, r_options);
+}
+
void Engine::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_ticks_per_second", "physics_ticks_per_second"), &Engine::set_physics_ticks_per_second);
ClassDB::bind_method(D_METHOD("get_physics_ticks_per_second"), &Engine::get_physics_ticks_per_second);
diff --git a/core/core_bind.h b/core/core_bind.h
index 715e26cf23..94d95f2ce9 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -337,6 +337,7 @@ public:
Vector<Vector3> segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray<Plane> &p_planes);
Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane);
+ Vector<int32_t> tetrahedralize_delaunay(const Vector<Vector3> &p_points);
Geometry3D() { singleton = this; }
};
@@ -434,17 +435,17 @@ public:
bool can_instantiate(const StringName &p_class) const;
Variant instantiate(const StringName &p_class) const;
- bool class_has_signal(StringName p_class, StringName p_signal) const;
- Dictionary class_get_signal(StringName p_class, StringName p_signal) const;
- TypedArray<Dictionary> class_get_signal_list(StringName p_class, bool p_no_inheritance = false) const;
+ bool class_has_signal(const StringName &p_class, const StringName &p_signal) const;
+ Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const;
+ TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const;
- TypedArray<Dictionary> class_get_property_list(StringName p_class, bool p_no_inheritance = false) const;
+ TypedArray<Dictionary> class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const;
Variant class_get_property(Object *p_object, const StringName &p_property) const;
Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const;
- bool class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const;
+ bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
- TypedArray<Dictionary> class_get_method_list(StringName p_class, bool p_no_inheritance = false) const;
+ TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
@@ -455,7 +456,7 @@ public:
PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
- bool is_class_enabled(StringName p_class) const;
+ bool is_class_enabled(const StringName &p_class) const;
ClassDB() {}
~ClassDB() {}
@@ -527,6 +528,8 @@ public:
void set_print_error_messages(bool p_enabled);
bool is_printing_error_messages() const;
+ virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+
Engine() { singleton = this; }
};
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 2f70fdf219..3b96fc20c6 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -845,7 +845,7 @@ bool CoreConstants::is_global_enum(const StringName &p_enum) {
return _global_enums.has(p_enum);
}
-void CoreConstants::get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values) {
+void CoreConstants::get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *p_values) {
ERR_FAIL_NULL_MSG(p_values, "Trying to get enum values with null map.");
ERR_FAIL_COND_MSG(!_global_enums.has(p_enum), "Trying to get values of non-existing enum.");
for (const _CoreConstant &constant : _global_enums[p_enum]) {
diff --git a/core/core_constants.h b/core/core_constants.h
index 51842490c8..82d626c749 100644
--- a/core/core_constants.h
+++ b/core/core_constants.h
@@ -45,7 +45,7 @@ public:
static bool is_global_constant(const StringName &p_name);
static int get_global_constant_index(const StringName &p_name);
static bool is_global_enum(const StringName &p_enum);
- static void get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values);
+ static void get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *p_values);
};
#endif // CORE_CONSTANTS_H
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
index 32dc060aa2..0cce44d02f 100644
--- a/core/debugger/engine_debugger.cpp
+++ b/core/debugger/engine_debugger.cpp
@@ -162,7 +162,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, Ve
singleton_script_debugger->set_skip_breakpoints(p_skip_breakpoints);
for (int i = 0; i < p_breakpoints.size(); i++) {
- String bp = p_breakpoints[i];
+ const String &bp = p_breakpoints[i];
int sp = bp.rfind(":");
ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index a817ea871d..d3b0039e72 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -36,6 +36,7 @@
#include "core/debugger/engine_profiler.h"
#include "core/debugger/script_debugger.h"
#include "core/input/input.h"
+#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
@@ -435,9 +436,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
messages.insert(Thread::get_caller_id(), List<Message>());
}
- mutex.lock();
while (is_peer_connected()) {
- mutex.unlock();
flush_output();
_poll_messages();
@@ -515,8 +514,9 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
_send_stack_vars(globals, globals_vals, 2);
} else if (command == "reload_scripts") {
+ script_paths_to_reload = data;
+ } else if (command == "reload_all_scripts") {
reload_all_scripts = true;
-
} else if (command == "breakpoint") {
ERR_FAIL_COND(data.size() < 3);
bool set = data[2];
@@ -591,19 +591,36 @@ void RemoteDebugger::poll_events(bool p_is_idle) {
}
// Reload scripts during idle poll only.
- if (p_is_idle && reload_all_scripts) {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->reload_all_scripts();
+ if (p_is_idle) {
+ if (reload_all_scripts) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->reload_all_scripts();
+ }
+ reload_all_scripts = false;
+ } else if (!script_paths_to_reload.is_empty()) {
+ Array scripts_to_reload;
+ for (int i = 0; i < script_paths_to_reload.size(); ++i) {
+ String path = script_paths_to_reload[i];
+ Error err = OK;
+ Ref<Script> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
+ ERR_CONTINUE_MSG(err != OK, vformat("Could not reload script '%s': %s", path, error_names[err]));
+ ERR_CONTINUE_MSG(script.is_null(), vformat("Could not reload script '%s': Not a script!", path, error_names[err]));
+ scripts_to_reload.push_back(script);
+ }
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->reload_scripts(scripts_to_reload, true);
+ }
}
- reload_all_scripts = false;
+ script_paths_to_reload.clear();
}
}
Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
r_captured = true;
if (p_cmd == "reload_scripts") {
+ script_paths_to_reload = p_data;
+ } else if (p_cmd == "reload_all_scripts") {
reload_all_scripts = true;
-
} else if (p_cmd == "breakpoint") {
ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA);
bool set = p_data[2];
diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h
index 7c399178c6..519a90e7cc 100644
--- a/core/debugger/remote_debugger.h
+++ b/core/debugger/remote_debugger.h
@@ -74,6 +74,7 @@ private:
int warn_count = 0;
int last_reset = 0;
bool reload_all_scripts = false;
+ Array script_paths_to_reload;
// Make handlers and send_message thread safe.
Mutex mutex;
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 2bac1f6592..ce01531b5c 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -666,12 +666,12 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte
HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions;
-void GDExtension::register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
+void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
gdextension_interface_functions.insert(p_function_name, p_function_pointer);
}
-GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p_function_name) {
+GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) {
GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + ".");
return *function;
@@ -715,10 +715,8 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb
#endif
Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &library_path);
- if (err != OK) {
- ERR_PRINT("GDExtension dynamic library not found: " + abs_path);
- return err;
- }
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
// If we copied the file, let's change the library path to point at the original,
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 0d20b8e50c..0b39581751 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -154,8 +154,8 @@ public:
void initialize_library(InitializationLevel p_level);
void deinitialize_library(InitializationLevel p_level);
- static void register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer);
- static GDExtensionInterfaceFunctionPtr get_interface_function(StringName p_function_name);
+ static void register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer);
+ static GDExtensionInterfaceFunctionPtr get_interface_function(const StringName &p_function_name);
static void initialize_gdextensions();
static void finalize_gdextensions();
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 8f976cbaa3..2ba4b1d1e8 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -198,6 +198,7 @@ void Input::get_argument_options(const StringName &p_function, int p_idx, List<S
r_options->push_back(name.quote());
}
}
+ Object::get_argument_options(p_function, p_idx, r_options);
}
void Input::VelocityTrack::update(const Vector2 &p_delta_p) {
@@ -1518,7 +1519,7 @@ void Input::add_joy_mapping(String p_mapping, bool p_update_existing) {
parse_mapping(p_mapping);
if (p_update_existing) {
Vector<String> entry = p_mapping.split(",");
- String uid = entry[0];
+ const String &uid = entry[0];
for (KeyValue<int, Joypad> &E : joy_names) {
Joypad &joy = E.value;
if (joy.uid == uid) {
diff --git a/core/input/input_builders.py b/core/input/input_builders.py
index e98e2441e2..94c566493e 100644
--- a/core/input/input_builders.py
+++ b/core/input/input_builders.py
@@ -45,10 +45,10 @@ def make_default_controller_mappings(target, source, env):
platform_mappings[current_platform][guid] = line
platform_variables = {
- "Linux": "#if LINUXBSD_ENABLED",
+ "Linux": "#ifdef LINUXBSD_ENABLED",
"Windows": "#ifdef WINDOWS_ENABLED",
"Mac OS X": "#ifdef MACOS_ENABLED",
- "Android": "#if defined(__ANDROID__)",
+ "Android": "#ifdef ANDROID_ENABLED",
"iOS": "#ifdef IOS_ENABLED",
"Web": "#ifdef WEB_ENABLED",
}
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 78b9ada884..70041ecfd6 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -754,7 +754,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins_with_featur
String fullname = E.key;
Vector<String> split = fullname.split(".");
- String name = split[0];
+ const String &name = split[0];
String override_for = split.size() > 1 ? split[1] : String();
if (!override_for.is_empty() && OS::get_singleton()->has_feature(override_for)) {
@@ -766,7 +766,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins_with_featur
String fullname = E.key;
Vector<String> split = fullname.split(".");
- String name = split[0];
+ const String &name = split[0];
String override_for = split.size() > 1 ? split[1] : String();
if (builtins_with_overrides.has(name) && override_for.is_empty()) {
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 265d9ef56c..5a4d6dd099 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -491,7 +491,7 @@ PackedData::PackedDir *DirAccessPack::_find_dir(String p_dir) {
}
for (int i = 0; i < paths.size(); i++) {
- String p = paths[i];
+ const String &p = paths[i];
if (p == ".") {
continue;
} else if (p == "..") {
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 09505ea05d..833fd1adc3 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -73,7 +73,7 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
Array keys = p_dict.keys();
for (int i = 0; i < keys.size(); ++i) {
String encoded_key = String(keys[i]).uri_encode();
- Variant value = p_dict[keys[i]];
+ const Variant &value = p_dict[keys[i]];
switch (value.get_type()) {
case Variant::ARRAY: {
// Repeat the key with every values
diff --git a/core/io/image.cpp b/core/io/image.cpp
index c72064e4f7..9aa7c9794a 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -3013,6 +3013,7 @@ void Image::fill_rect(const Rect2i &p_rect, const Color &p_color) {
}
ImageMemLoadFunc Image::_png_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_png_mem_unpacker_func = nullptr;
ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr;
diff --git a/core/io/image.h b/core/io/image.h
index a21d05187b..be308b0ac1 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -145,6 +145,7 @@ public:
};
static ImageMemLoadFunc _png_mem_loader_func;
+ static ImageMemLoadFunc _png_mem_unpacker_func;
static ImageMemLoadFunc _jpg_mem_loader_func;
static ImageMemLoadFunc _webp_mem_loader_func;
static ImageMemLoadFunc _tga_mem_loader_func;
@@ -312,8 +313,8 @@ public:
Error save_jpg(const String &p_path, float p_quality = 0.75) const;
Vector<uint8_t> save_png_to_buffer() const;
Vector<uint8_t> save_jpg_to_buffer(float p_quality = 0.75) const;
- Vector<uint8_t> save_exr_to_buffer(bool p_grayscale) const;
- Error save_exr(const String &p_path, bool p_grayscale) const;
+ Vector<uint8_t> save_exr_to_buffer(bool p_grayscale = false) const;
+ Error save_exr(const String &p_path, bool p_grayscale = false) const;
Error save_webp(const String &p_path, const bool p_lossy = false, const float p_quality = 0.75f) const;
Vector<uint8_t> save_webp_to_buffer(const bool p_lossy = false, const float p_quality = 0.75f) const;
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
new file mode 100644
index 0000000000..86737609bf
--- /dev/null
+++ b/core/io/plist.cpp
@@ -0,0 +1,868 @@
+/**************************************************************************/
+/* plist.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "plist.h"
+
+PList::PLNodeType PListNode::get_type() const {
+ return data_type;
+}
+
+Variant PListNode::get_value() const {
+ switch (data_type) {
+ case PList::PL_NODE_TYPE_NIL: {
+ return Variant();
+ } break;
+ case PList::PL_NODE_TYPE_STRING: {
+ return String::utf8(data_string.get_data());
+ } break;
+ case PList::PL_NODE_TYPE_ARRAY: {
+ Array arr;
+ for (const Ref<PListNode> &E : data_array) {
+ arr.push_back(E);
+ }
+ return arr;
+ } break;
+ case PList::PL_NODE_TYPE_DICT: {
+ Dictionary dict;
+ for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
+ dict[E.key] = E.value;
+ }
+ return dict;
+ } break;
+ case PList::PL_NODE_TYPE_BOOLEAN: {
+ return data_bool;
+ } break;
+ case PList::PL_NODE_TYPE_INTEGER: {
+ return data_int;
+ } break;
+ case PList::PL_NODE_TYPE_REAL: {
+ return data_real;
+ } break;
+ case PList::PL_NODE_TYPE_DATA: {
+ int strlen = data_string.length();
+
+ size_t arr_len = 0;
+ Vector<uint8_t> buf;
+ {
+ buf.resize(strlen / 4 * 3 + 1);
+ uint8_t *w = buf.ptrw();
+
+ ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf.size(), &arr_len, (unsigned char *)data_string.get_data(), strlen) != OK, Vector<uint8_t>());
+ }
+ buf.resize(arr_len);
+ return buf;
+ } break;
+ case PList::PL_NODE_TYPE_DATE: {
+ return String(data_string.get_data());
+ } break;
+ }
+ return Variant();
+}
+
+Ref<PListNode> PListNode::new_node(const Variant &p_value) {
+ Ref<PListNode> node;
+ node.instantiate();
+
+ switch (p_value.get_type()) {
+ case Variant::NIL: {
+ node->data_type = PList::PL_NODE_TYPE_NIL;
+ } break;
+ case Variant::BOOL: {
+ node->data_type = PList::PL_NODE_TYPE_BOOLEAN;
+ node->data_bool = p_value;
+ } break;
+ case Variant::INT: {
+ node->data_type = PList::PL_NODE_TYPE_INTEGER;
+ node->data_int = p_value;
+ } break;
+ case Variant::FLOAT: {
+ node->data_type = PList::PL_NODE_TYPE_REAL;
+ node->data_real = p_value;
+ } break;
+ case Variant::STRING_NAME:
+ case Variant::STRING: {
+ node->data_type = PList::PL_NODE_TYPE_STRING;
+ node->data_string = p_value.operator String().utf8();
+ } break;
+ case Variant::DICTIONARY: {
+ node->data_type = PList::PL_NODE_TYPE_DICT;
+ Dictionary dict = p_value;
+ const Variant *next = dict.next(nullptr);
+ while (next) {
+ Ref<PListNode> sub_node = dict[*next];
+ ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid dictionary element, should be PListNode.");
+ node->data_dict[*next] = sub_node;
+ next = dict.next(next);
+ }
+ } break;
+ case Variant::ARRAY: {
+ node->data_type = PList::PL_NODE_TYPE_ARRAY;
+ Array ar = p_value;
+ for (int i = 0; i < ar.size(); i++) {
+ Ref<PListNode> sub_node = ar[i];
+ ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid array element, should be PListNode.");
+ node->data_array.push_back(sub_node);
+ }
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ node->data_type = PList::PL_NODE_TYPE_DATA;
+ PackedByteArray buf = p_value;
+ node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(Ref<PListNode>(), "Unsupported data type.");
+ } break;
+ }
+ return node;
+}
+
+Ref<PListNode> PListNode::new_array() {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_dict() {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_string(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING;
+ node->data_string = p_string.utf8();
+ return node;
+}
+
+Ref<PListNode> PListNode::new_data(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA;
+ node->data_string = p_string.utf8();
+ return node;
+}
+
+Ref<PListNode> PListNode::new_date(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE;
+ node->data_string = p_string.utf8();
+ node->data_real = (double)Time::get_singleton()->get_unix_time_from_datetime_string(p_string) - 978307200.0;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_bool(bool p_bool) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN;
+ node->data_bool = p_bool;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_int(int64_t p_int) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER;
+ node->data_int = p_int;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_real(double p_real) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL;
+ node->data_real = p_real;
+ return node;
+}
+
+bool PListNode::push_subnode(const Ref<PListNode> &p_node, const String &p_key) {
+ ERR_FAIL_COND_V(p_node.is_null(), false);
+ if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) {
+ ERR_FAIL_COND_V(p_key.is_empty(), false);
+ ERR_FAIL_COND_V(data_dict.has(p_key), false);
+ data_dict[p_key] = p_node;
+ return true;
+ } else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
+ data_array.push_back(p_node);
+ return true;
+ } else {
+ ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY.");
+ }
+}
+
+size_t PListNode::get_asn1_size(uint8_t p_len_octets) const {
+ // Get size of all data, excluding type and size information.
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ return 0;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATA:
+ case PList::PLNodeType::PL_NODE_TYPE_DATE: {
+ ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ return data_string.length();
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ return 1;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER:
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ return 4;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ size_t size = 0;
+ for (int i = 0; i < data_array.size(); i++) {
+ size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets);
+ }
+ return size;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ size_t size = 0;
+
+ for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
+ size += 1 + _asn1_size_len(p_len_octets); // Sequence.
+ size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key.
+ size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value.
+ }
+ return size;
+ } break;
+ default: {
+ return 0;
+ } break;
+ }
+}
+
+int PListNode::_asn1_size_len(uint8_t p_len_octets) {
+ if (p_len_octets > 1) {
+ return p_len_octets + 1;
+ } else {
+ return 1;
+ }
+}
+
+void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const {
+ uint32_t size = get_asn1_size(p_len_octets);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+}
+
+bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const {
+ // Convert to binary ASN1 stream.
+ bool valid = true;
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ // Nothing to store.
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATE:
+ case PList::PLNodeType::PL_NODE_TYPE_DATA: {
+ ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ p_stream.push_back(0x0C);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 0; i < data_string.size(); i++) {
+ p_stream.push_back(data_string[i]);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ p_stream.push_back(0x01);
+ store_asn1_size(p_stream, p_len_octets);
+ if (data_bool) {
+ p_stream.push_back(0x01);
+ } else {
+ p_stream.push_back(0x00);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
+ p_stream.push_back(0x02);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 4; i >= 0; i--) {
+ uint8_t x = (data_int >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ p_stream.push_back(0x03);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 4; i >= 0; i--) {
+ uint8_t x = (data_int >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ p_stream.push_back(0x30); // Sequence.
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 0; i < data_array.size(); i++) {
+ valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ p_stream.push_back(0x31); // Set.
+ store_asn1_size(p_stream, p_len_octets);
+ for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
+ CharString cs = E.key.utf8();
+ uint32_t size = cs.length();
+
+ // Sequence.
+ p_stream.push_back(0x30);
+ uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (seq_size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ // Key.
+ p_stream.push_back(0x0C);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ for (uint32_t i = 0; i < size; i++) {
+ p_stream.push_back(cs[i]);
+ }
+ // Value.
+ valid = valid && E.value->store_asn1(p_stream, p_len_octets);
+ }
+ } break;
+ }
+ return valid;
+}
+
+void PListNode::store_text(String &p_stream, uint8_t p_indent) const {
+ // Convert to text XML stream.
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ // Nothing to store.
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATA: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<data>\n";
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += data_string + "\n";
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</data>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATE: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<date>";
+ p_stream += data_string;
+ p_stream += "</date>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<string>";
+ p_stream += String::utf8(data_string);
+ p_stream += "</string>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ p_stream += String("\t").repeat(p_indent);
+ if (data_bool) {
+ p_stream += "<true/>\n";
+ } else {
+ p_stream += "<false/>\n";
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<integer>";
+ p_stream += itos(data_int);
+ p_stream += "</integer>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<real>";
+ p_stream += rtos(data_real);
+ p_stream += "</real>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<array>\n";
+ for (int i = 0; i < data_array.size(); i++) {
+ data_array[i]->store_text(p_stream, p_indent + 1);
+ }
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</array>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<dict>\n";
+ for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
+ p_stream += String("\t").repeat(p_indent + 1);
+ p_stream += "<key>";
+ p_stream += E.key;
+ p_stream += "</key>\n";
+ E.value->store_text(p_stream, p_indent + 1);
+ }
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</dict>\n";
+ } break;
+ }
+}
+
+/*************************************************************************/
+
+PList::PList() {
+ root = PListNode::new_dict();
+}
+
+PList::PList(const String &p_string) {
+ String err_str;
+ bool ok = load_string(p_string, err_str);
+ ERR_FAIL_COND_MSG(!ok, "PList: " + err_str);
+}
+
+uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
+ uint64_t pos = p_file->get_position();
+ uint64_t ret = 0;
+ switch (p_size) {
+ case 1: {
+ ret = p_file->get_8();
+ } break;
+ case 2: {
+ ret = BSWAP16(p_file->get_16());
+ } break;
+ case 3: {
+ ret = BSWAP32(p_file->get_32() & 0x00FFFFFF);
+ } break;
+ case 4: {
+ ret = BSWAP32(p_file->get_32());
+ } break;
+ case 5: {
+ ret = BSWAP64(p_file->get_64() & 0x000000FFFFFFFFFF);
+ } break;
+ case 6: {
+ ret = BSWAP64(p_file->get_64() & 0x0000FFFFFFFFFFFF);
+ } break;
+ case 7: {
+ ret = BSWAP64(p_file->get_64() & 0x00FFFFFFFFFFFFFF);
+ } break;
+ case 8: {
+ ret = BSWAP64(p_file->get_64());
+ } break;
+ default: {
+ ret = 0;
+ }
+ }
+ p_file->seek(pos + p_size);
+
+ return ret;
+}
+
+Ref<PListNode> PList::read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx) {
+ Ref<PListNode> node;
+ node.instantiate();
+
+ uint64_t ot_off = trailer.offset_table_start + p_offset_idx * trailer.offset_size;
+ p_file->seek(ot_off);
+ uint64_t marker_off = read_bplist_var_size_int(p_file, trailer.offset_size);
+ ERR_FAIL_COND_V_MSG(marker_off == 0, Ref<PListNode>(), "Invalid marker size.");
+
+ p_file->seek(marker_off);
+ uint8_t marker = p_file->get_8();
+ uint8_t marker_type = marker & 0xF0;
+ uint64_t marker_size = marker & 0x0F;
+
+ switch (marker_type) {
+ case 0x00: {
+ if (marker_size == 0x00) {
+ node->data_type = PL_NODE_TYPE_NIL;
+ } else if (marker_size == 0x08) {
+ node->data_type = PL_NODE_TYPE_BOOLEAN;
+ node->data_bool = false;
+ } else if (marker_size == 0x09) {
+ node->data_type = PL_NODE_TYPE_BOOLEAN;
+ node->data_bool = true;
+ } else {
+ ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid nil/bool marker value.");
+ }
+ } break;
+ case 0x10: {
+ node->data_type = PL_NODE_TYPE_INTEGER;
+ node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
+ } break;
+ case 0x20: {
+ node->data_type = PL_NODE_TYPE_REAL;
+ node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
+ } break;
+ case 0x30: {
+ node->data_type = PL_NODE_TYPE_DATE;
+ node->data_int = BSWAP64(p_file->get_64());
+ node->data_string = Time::get_singleton()->get_datetime_string_from_unix_time(node->data_real + 978307200.0).utf8();
+ } break;
+ case 0x40: {
+ if (marker_size == 0x0F) {
+ uint8_t ext = p_file->get_8() & 0xF;
+ marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
+ }
+ node->data_type = PL_NODE_TYPE_DATA;
+ PackedByteArray buf;
+ buf.resize(marker_size + 1);
+ p_file->get_buffer(reinterpret_cast<uint8_t *>(buf.ptrw()), marker_size);
+ node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
+ } break;
+ case 0x50: {
+ if (marker_size == 0x0F) {
+ uint8_t ext = p_file->get_8() & 0xF;
+ marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
+ }
+ node->data_type = PL_NODE_TYPE_STRING;
+ node->data_string.resize(marker_size + 1);
+ p_file->get_buffer(reinterpret_cast<uint8_t *>(node->data_string.ptrw()), marker_size);
+ } break;
+ case 0x60: {
+ if (marker_size == 0x0F) {
+ uint8_t ext = p_file->get_8() & 0xF;
+ marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
+ }
+ Char16String cs16;
+ cs16.resize(marker_size + 1);
+ for (uint64_t i = 0; i < marker_size; i++) {
+ cs16[i] = BSWAP16(p_file->get_16());
+ }
+ node->data_type = PL_NODE_TYPE_STRING;
+ node->data_string = String::utf16(cs16.ptr(), cs16.length()).utf8();
+ } break;
+ case 0x80: {
+ node->data_type = PL_NODE_TYPE_INTEGER;
+ node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, marker_size + 1));
+ } break;
+ case 0xA0:
+ case 0xC0: {
+ if (marker_size == 0x0F) {
+ uint8_t ext = p_file->get_8() & 0xF;
+ marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
+ }
+ uint64_t pos = p_file->get_position();
+
+ node->data_type = PL_NODE_TYPE_ARRAY;
+ for (uint64_t i = 0; i < marker_size; i++) {
+ p_file->seek(pos + trailer.ref_size * i);
+ uint64_t ref = read_bplist_var_size_int(p_file, trailer.ref_size);
+
+ Ref<PListNode> element = read_bplist_obj(p_file, ref);
+ ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
+ node->data_array.push_back(element);
+ }
+ } break;
+ case 0xD0: {
+ if (marker_size == 0x0F) {
+ uint8_t ext = p_file->get_8() & 0xF;
+ marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
+ }
+ uint64_t pos = p_file->get_position();
+
+ node->data_type = PL_NODE_TYPE_DICT;
+ for (uint64_t i = 0; i < marker_size; i++) {
+ p_file->seek(pos + trailer.ref_size * i);
+ uint64_t key_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
+
+ p_file->seek(pos + trailer.ref_size * (i + marker_size));
+ uint64_t obj_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
+
+ Ref<PListNode> element_key = read_bplist_obj(p_file, key_ref);
+ ERR_FAIL_COND_V(element_key.is_null() || element_key->data_type != PL_NODE_TYPE_STRING, Ref<PListNode>());
+ Ref<PListNode> element = read_bplist_obj(p_file, obj_ref);
+ ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
+ node->data_dict[String::utf8(element_key->data_string.ptr(), element_key->data_string.length())] = element;
+ }
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid marker type.");
+ }
+ }
+ return node;
+}
+
+bool PList::load_file(const String &p_filename) {
+ root = Ref<PListNode>();
+
+ Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ);
+ if (fb.is_null()) {
+ return false;
+ }
+
+ unsigned char magic[8];
+ fb->get_buffer(magic, 8);
+
+ if (String((const char *)magic, 8) == "bplist00") {
+ fb->seek_end(-26);
+ trailer.offset_size = fb->get_8();
+ trailer.ref_size = fb->get_8();
+ trailer.object_num = BSWAP64(fb->get_64());
+ trailer.root_offset_idx = BSWAP64(fb->get_64());
+ trailer.offset_table_start = BSWAP64(fb->get_64());
+ root = read_bplist_obj(fb, trailer.root_offset_idx);
+
+ return root.is_valid();
+ } else {
+ // Load text plist.
+ Error err;
+ Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_filename, &err);
+ ERR_FAIL_COND_V(err != OK, false);
+
+ String ret;
+ ret.parse_utf8((const char *)array.ptr(), array.size());
+ String err_str;
+ bool ok = load_string(ret, err_str);
+ ERR_FAIL_COND_V_MSG(!ok, false, "PList: " + err_str);
+
+ return true;
+ }
+}
+
+bool PList::load_string(const String &p_string, String &r_err_out) {
+ root = Ref<PListNode>();
+
+ int pos = 0;
+ bool in_plist = false;
+ bool done_plist = false;
+ List<Ref<PListNode>> stack;
+ String key;
+ while (pos >= 0) {
+ int open_token_s = p_string.find("<", pos);
+ if (open_token_s == -1) {
+ r_err_out = "Unexpected end of data. No tags found.";
+ return false;
+ }
+ int open_token_e = p_string.find(">", open_token_s);
+ pos = open_token_e;
+
+ String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1);
+ if (token.is_empty()) {
+ r_err_out = "Invalid token name.";
+ return false;
+ }
+ String value;
+ if (token[0] == '?' || token[0] == '!') { // Skip <?xml ... ?> and <!DOCTYPE ... >
+ int end_token_e = p_string.find(">", open_token_s);
+ pos = end_token_e;
+ continue;
+ }
+
+ if (token.find("plist", 0) == 0) {
+ in_plist = true;
+ continue;
+ }
+
+ if (token == "/plist") {
+ done_plist = true;
+ break;
+ }
+
+ if (!in_plist) {
+ r_err_out = "Node outside of <plist> tag.";
+ return false;
+ }
+
+ if (token == "dict") {
+ if (!stack.is_empty()) {
+ // Add subnode end enter it.
+ Ref<PListNode> dict = PListNode::new_dict();
+ dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
+ if (!stack.back()->get()->push_subnode(dict, key)) {
+ r_err_out = "Can't push subnode, invalid parent type.";
+ return false;
+ }
+ stack.push_back(dict);
+ } else {
+ // Add root node.
+ if (!root.is_null()) {
+ r_err_out = "Root node already set.";
+ return false;
+ }
+ Ref<PListNode> dict = PListNode::new_dict();
+ stack.push_back(dict);
+ root = dict;
+ }
+ continue;
+ }
+
+ if (token == "/dict") {
+ // Exit current dict.
+ if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) {
+ r_err_out = "Mismatched </dict> tag.";
+ return false;
+ }
+ stack.pop_back();
+ continue;
+ }
+
+ if (token == "array") {
+ if (!stack.is_empty()) {
+ // Add subnode end enter it.
+ Ref<PListNode> arr = PListNode::new_array();
+ if (!stack.back()->get()->push_subnode(arr, key)) {
+ r_err_out = "Can't push subnode, invalid parent type.";
+ return false;
+ }
+ stack.push_back(arr);
+ } else {
+ // Add root node.
+ if (!root.is_null()) {
+ r_err_out = "Root node already set.";
+ return false;
+ }
+ Ref<PListNode> arr = PListNode::new_array();
+ stack.push_back(arr);
+ root = arr;
+ }
+ continue;
+ }
+
+ if (token == "/array") {
+ // Exit current array.
+ if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
+ r_err_out = "Mismatched </array> tag.";
+ return false;
+ }
+ stack.pop_back();
+ continue;
+ }
+
+ if (token[token.length() - 1] == '/') {
+ token = token.substr(0, token.length() - 1);
+ } else {
+ int end_token_s = p_string.find("</", pos);
+ if (end_token_s == -1) {
+ r_err_out = vformat("Mismatched <%s> tag.", token);
+ return false;
+ }
+ int end_token_e = p_string.find(">", end_token_s);
+ pos = end_token_e;
+ String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2);
+ if (end_token != token) {
+ r_err_out = vformat("Mismatched <%s> and <%s> token pair.", token, end_token);
+ return false;
+ }
+ value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1);
+ }
+ if (token == "key") {
+ key = value;
+ } else {
+ Ref<PListNode> var = nullptr;
+ if (token == "true") {
+ var = PListNode::new_bool(true);
+ } else if (token == "false") {
+ var = PListNode::new_bool(false);
+ } else if (token == "integer") {
+ var = PListNode::new_int(value.to_int());
+ } else if (token == "real") {
+ var = PListNode::new_real(value.to_float());
+ } else if (token == "string") {
+ var = PListNode::new_string(value);
+ } else if (token == "data") {
+ var = PListNode::new_data(value);
+ } else if (token == "date") {
+ var = PListNode::new_date(value);
+ } else {
+ r_err_out = vformat("Invalid value type: %s.", token);
+ return false;
+ }
+ if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) {
+ r_err_out = "Can't push subnode, invalid parent type.";
+ return false;
+ }
+ }
+ }
+ if (!stack.is_empty() || !done_plist) {
+ r_err_out = "Unexpected end of data. Root node is not closed.";
+ return false;
+ }
+ return true;
+}
+
+PackedByteArray PList::save_asn1() const {
+ if (root == nullptr) {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node.");
+ }
+ size_t size = root->get_asn1_size(1);
+ uint8_t len_octets = 0;
+ if (size < 0x80) {
+ len_octets = 1;
+ } else {
+ size = root->get_asn1_size(2);
+ if (size < 0xFFFF) {
+ len_octets = 2;
+ } else {
+ size = root->get_asn1_size(3);
+ if (size < 0xFFFFFF) {
+ len_octets = 3;
+ } else {
+ size = root->get_asn1_size(4);
+ if (size < 0xFFFFFFFF) {
+ len_octets = 4;
+ } else {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB.");
+ }
+ }
+ }
+ }
+
+ PackedByteArray ret;
+ if (!root->store_asn1(ret, len_octets)) {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error.");
+ }
+ return ret;
+}
+
+String PList::save_text() const {
+ if (root == nullptr) {
+ ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
+ }
+
+ String ret;
+ ret += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
+ ret += "<plist version=\"1.0\">\n";
+
+ root->store_text(ret, 0);
+
+ ret += "</plist>\n\n";
+ return ret;
+}
+
+Ref<PListNode> PList::get_root() {
+ return root;
+}
diff --git a/core/io/plist.h b/core/io/plist.h
new file mode 100644
index 0000000000..7d8b8ef0b4
--- /dev/null
+++ b/core/io/plist.h
@@ -0,0 +1,128 @@
+/**************************************************************************/
+/* plist.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef PLIST_H
+#define PLIST_H
+
+// Property list file format (application/x-plist) parser, property list ASN-1 serialization.
+
+#include "core/crypto/crypto_core.h"
+#include "core/io/file_access.h"
+#include "core/os/time.h"
+
+class PListNode;
+
+class PList : public RefCounted {
+ friend class PListNode;
+
+public:
+ enum PLNodeType {
+ PL_NODE_TYPE_NIL,
+ PL_NODE_TYPE_STRING,
+ PL_NODE_TYPE_ARRAY,
+ PL_NODE_TYPE_DICT,
+ PL_NODE_TYPE_BOOLEAN,
+ PL_NODE_TYPE_INTEGER,
+ PL_NODE_TYPE_REAL,
+ PL_NODE_TYPE_DATA,
+ PL_NODE_TYPE_DATE,
+ };
+
+private:
+ struct PListTrailer {
+ uint8_t offset_size;
+ uint8_t ref_size;
+ uint64_t object_num;
+ uint64_t root_offset_idx;
+ uint64_t offset_table_start;
+ };
+
+ PListTrailer trailer;
+ Ref<PListNode> root;
+
+ uint64_t read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size);
+ Ref<PListNode> read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx);
+
+public:
+ PList();
+ PList(const String &p_string);
+
+ bool load_file(const String &p_filename);
+ bool load_string(const String &p_string, String &r_err_out);
+
+ PackedByteArray save_asn1() const;
+ String save_text() const;
+
+ Ref<PListNode> get_root();
+};
+
+/*************************************************************************/
+
+class PListNode : public RefCounted {
+ static int _asn1_size_len(uint8_t p_len_octets);
+
+public:
+ PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL;
+
+ CharString data_string;
+ Vector<Ref<PListNode>> data_array;
+ HashMap<String, Ref<PListNode>> data_dict;
+ union {
+ int64_t data_int;
+ bool data_bool;
+ double data_real;
+ };
+
+ PList::PLNodeType get_type() const;
+ Variant get_value() const;
+
+ static Ref<PListNode> new_node(const Variant &p_value);
+ static Ref<PListNode> new_array();
+ static Ref<PListNode> new_dict();
+ static Ref<PListNode> new_string(const String &p_string);
+ static Ref<PListNode> new_data(const String &p_string);
+ static Ref<PListNode> new_date(const String &p_string);
+ static Ref<PListNode> new_bool(bool p_bool);
+ static Ref<PListNode> new_int(int64_t p_int);
+ static Ref<PListNode> new_real(double p_real);
+
+ bool push_subnode(const Ref<PListNode> &p_node, const String &p_key = "");
+
+ size_t get_asn1_size(uint8_t p_len_octets) const;
+
+ void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const;
+ bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const;
+ void store_text(String &p_stream, uint8_t p_indent) const;
+
+ PListNode() {}
+ ~PListNode() {}
+};
+
+#endif // PLIST_H
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 04ecabaf27..daa57be76f 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -204,7 +204,58 @@ void Resource::reload_from_file() {
copy_from(s);
}
-Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &remap_cache) {
+void Resource::_dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
+ switch (r_variant.get_type()) {
+ case Variant::ARRAY: {
+ Array a = r_variant;
+ for (int i = 0; i < a.size(); i++) {
+ _dupe_sub_resources(a[i], p_for_scene, p_remap_cache);
+ }
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = r_variant;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ for (Variant &k : keys) {
+ if (k.get_type() == Variant::OBJECT) {
+ // Replace in dictionary key.
+ Ref<Resource> sr = k;
+ if (sr.is_valid() && sr->is_local_to_scene()) {
+ if (p_remap_cache.has(sr)) {
+ d[p_remap_cache[sr]] = d[k];
+ d.erase(k);
+ } else {
+ Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
+ d[dupe] = d[k];
+ d.erase(k);
+ p_remap_cache[sr] = dupe;
+ }
+ }
+ } else {
+ _dupe_sub_resources(k, p_for_scene, p_remap_cache);
+ }
+
+ _dupe_sub_resources(d[k], p_for_scene, p_remap_cache);
+ }
+ } break;
+ case Variant::OBJECT: {
+ Ref<Resource> sr = r_variant;
+ if (sr.is_valid() && sr->is_local_to_scene()) {
+ if (p_remap_cache.has(sr)) {
+ r_variant = p_remap_cache[sr];
+ } else {
+ Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
+ r_variant = dupe;
+ p_remap_cache[sr] = dupe;
+ }
+ }
+ } break;
+ default: {
+ }
+ }
+}
+
+Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
List<PropertyInfo> plist;
get_property_list(&plist);
@@ -217,21 +268,9 @@ Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
- Variant p = get(E.name);
- if (p.get_type() == Variant::OBJECT) {
- Ref<Resource> sr = p;
- if (sr.is_valid()) {
- if (sr->is_local_to_scene()) {
- if (remap_cache.has(sr)) {
- p = remap_cache[sr];
- } else {
- Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, remap_cache);
- p = dupe;
- remap_cache[sr] = dupe;
- }
- }
- }
- }
+ Variant p = get(E.name).duplicate(true);
+
+ _dupe_sub_resources(p, p_for_scene, p_remap_cache);
r->set(E.name, p);
}
@@ -239,7 +278,35 @@ Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref
return r;
}
-void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &remap_cache) {
+void Resource::_find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found) {
+ switch (p_variant.get_type()) {
+ case Variant::ARRAY: {
+ Array a = p_variant;
+ for (int i = 0; i < a.size(); i++) {
+ _find_sub_resources(a[i], p_resources_found);
+ }
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_variant;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ for (const Variant &k : keys) {
+ _find_sub_resources(k, p_resources_found);
+ _find_sub_resources(d[k], p_resources_found);
+ }
+ } break;
+ case Variant::OBJECT: {
+ Ref<Resource> r = p_variant;
+ if (r.is_valid()) {
+ p_resources_found.insert(r);
+ }
+ } break;
+ default: {
+ }
+ }
+}
+
+void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
List<PropertyInfo> plist;
get_property_list(&plist);
@@ -251,14 +318,15 @@ void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource
continue;
}
Variant p = get(E.name);
- if (p.get_type() == Variant::OBJECT) {
- Ref<Resource> sr = p;
- if (sr.is_valid()) {
- if (sr->is_local_to_scene()) {
- if (!remap_cache.has(sr)) {
- sr->configure_for_local_scene(p_for_scene, remap_cache);
- remap_cache[sr] = sr;
- }
+
+ HashSet<Ref<Resource>> sub_resources;
+ _find_sub_resources(p, sub_resources);
+
+ for (Ref<Resource> sr : sub_resources) {
+ if (sr->is_local_to_scene()) {
+ if (!p_remap_cache.has(sr)) {
+ sr->configure_for_local_scene(p_for_scene, p_remap_cache);
+ p_remap_cache[sr] = sr;
}
}
}
diff --git a/core/io/resource.h b/core/io/resource.h
index 610c2150db..b885b773ac 100644
--- a/core/io/resource.h
+++ b/core/io/resource.h
@@ -74,6 +74,9 @@ private:
SelfList<Resource> remapped_list;
+ void _dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
+ void _find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found);
+
protected:
virtual void _resource_path_changed();
static void _bind_methods();
@@ -111,8 +114,8 @@ public:
String get_scene_unique_id() const;
virtual Ref<Resource> duplicate(bool p_subresources = false) const;
- Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &remap_cache);
- void configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &remap_cache);
+ Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
+ void configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
void set_local_to_scene(bool p_enable);
bool is_local_to_scene() const;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 0c7764392a..ba11d84bce 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -340,7 +340,7 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
if (load_task.resource.is_valid()) {
if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- load_task.resource->set_path(load_task.local_path, load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
+ load_task.resource->set_path(load_task.local_path);
} else if (!load_task.local_path.is_resource_file()) {
load_task.resource->set_path_cache(load_task.local_path);
}
@@ -361,17 +361,6 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
if (_loaded_callback) {
_loaded_callback(load_task.resource, load_task.local_path);
}
- } else if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- Ref<Resource> existing = ResourceCache::get_ref(load_task.local_path);
- if (existing.is_valid()) {
- load_task.resource = existing;
- load_task.status = THREAD_LOAD_LOADED;
- load_task.progress = 1.0;
-
- if (_loaded_callback) {
- _loaded_callback(load_task.resource, load_task.local_path);
- }
- }
}
thread_load_mutex.unlock();
@@ -474,7 +463,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
load_task.type_hint = p_type_hint;
load_task.cache_mode = p_cache_mode;
load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE;
- if (p_cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) {
+ if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
Ref<Resource> existing = ResourceCache::get_ref(local_path);
if (existing.is_valid()) {
//referencing is fine
@@ -641,15 +630,16 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
if (load_task.task_id != 0) {
// Loading thread is in the worker pool.
- load_task.awaited = true;
thread_load_mutex.unlock();
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.
+ // The WorkerThreadPool has reported that the current task wants to await on an older one.
+ // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of
+ // resource loading that means that the task to wait for can be restarted here to break the
+ // cycle, with as much recursion into this process as needed.
+ // 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.");
+ print_verbose("ResourceLoader: Potential for deadlock detected in task dependency. Attempting to avoid it 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.
@@ -663,6 +653,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
} else {
DEV_ASSERT(err == OK);
thread_load_mutex.lock();
+ load_task.awaited = true;
}
} else {
// Loading thread is main or user thread.
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index 1c6c18b015..51ebea7f2c 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -120,9 +120,8 @@ Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path,
String local_path = ProjectSettings::get_singleton()->localize_path(path);
- Ref<Resource> rwcopy = p_resource;
if (p_flags & FLAG_CHANGE_PATH) {
- rwcopy->set_path(local_path);
+ p_resource->set_path(local_path);
}
err = saver[i]->save(p_resource, path, p_flags);
@@ -139,7 +138,7 @@ Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path,
#endif
if (p_flags & FLAG_CHANGE_PATH) {
- rwcopy->set_path(old_path);
+ p_resource->set_path(old_path);
}
if (save_callback && path.begins_with("res://")) {
diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp
index f0f160940d..fb54058bd9 100644
--- a/core/math/a_star.cpp
+++ b/core/math/a_star.cpp
@@ -69,7 +69,7 @@ void AStar3D::add_point(int64_t p_id, const Vector3 &p_pos, real_t p_weight_scal
}
Vector3 AStar3D::get_point_position(int64_t p_id) const {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_V_MSG(!p_exists, Vector3(), vformat("Can't get point's position. Point with id: %d doesn't exist.", p_id));
@@ -77,7 +77,7 @@ Vector3 AStar3D::get_point_position(int64_t p_id) const {
}
void AStar3D::set_point_position(int64_t p_id, const Vector3 &p_pos) {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's position. Point with id: %d doesn't exist.", p_id));
@@ -85,7 +85,7 @@ void AStar3D::set_point_position(int64_t p_id, const Vector3 &p_pos) {
}
real_t AStar3D::get_point_weight_scale(int64_t p_id) const {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_V_MSG(!p_exists, 0, vformat("Can't get point's weight scale. Point with id: %d doesn't exist.", p_id));
@@ -93,7 +93,7 @@ real_t AStar3D::get_point_weight_scale(int64_t p_id) const {
}
void AStar3D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's weight scale. Point with id: %d doesn't exist.", p_id));
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
@@ -102,7 +102,7 @@ void AStar3D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) {
}
void AStar3D::remove_point(int64_t p_id) {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_MSG(!p_exists, vformat("Can't remove point. Point with id: %d doesn't exist.", p_id));
@@ -130,11 +130,11 @@ void AStar3D::remove_point(int64_t p_id) {
void AStar3D::connect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) {
ERR_FAIL_COND_MSG(p_id == p_with_id, vformat("Can't connect point with id: %d to itself.", p_id));
- Point *a;
+ Point *a = nullptr;
bool from_exists = points.lookup(p_id, a);
ERR_FAIL_COND_MSG(!from_exists, vformat("Can't connect points. Point with id: %d doesn't exist.", p_id));
- Point *b;
+ Point *b = nullptr;
bool to_exists = points.lookup(p_with_id, b);
ERR_FAIL_COND_MSG(!to_exists, vformat("Can't connect points. Point with id: %d doesn't exist.", p_with_id));
@@ -166,11 +166,11 @@ void AStar3D::connect_points(int64_t p_id, int64_t p_with_id, bool bidirectional
}
void AStar3D::disconnect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) {
- Point *a;
+ Point *a = nullptr;
bool a_exists = points.lookup(p_id, a);
ERR_FAIL_COND_MSG(!a_exists, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_id));
- Point *b;
+ Point *b = nullptr;
bool b_exists = points.lookup(p_with_id, b);
ERR_FAIL_COND_MSG(!b_exists, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_with_id));
@@ -220,7 +220,7 @@ PackedInt64Array AStar3D::get_point_ids() {
}
Vector<int64_t> AStar3D::get_point_connections(int64_t p_id) {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_V_MSG(!p_exists, Vector<int64_t>(), vformat("Can't get point's connections. Point with id: %d doesn't exist.", p_id));
@@ -386,11 +386,11 @@ real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
return scost;
}
- Point *from_point;
+ Point *from_point = nullptr;
bool from_exists = points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- Point *to_point;
+ Point *to_point = nullptr;
bool to_exists = points.lookup(p_to_id, to_point);
ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
@@ -403,11 +403,11 @@ real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
return scost;
}
- Point *from_point;
+ Point *from_point = nullptr;
bool from_exists = points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
- Point *to_point;
+ Point *to_point = nullptr;
bool to_exists = points.lookup(p_to_id, to_point);
ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
@@ -415,11 +415,11 @@ real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
}
Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id) {
- Point *a;
+ Point *a = nullptr;
bool from_exists = points.lookup(p_from_id, a);
ERR_FAIL_COND_V_MSG(!from_exists, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id));
- Point *b;
+ Point *b = nullptr;
bool to_exists = points.lookup(p_to_id, b);
ERR_FAIL_COND_V_MSG(!to_exists, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id));
@@ -464,11 +464,11 @@ Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id) {
}
Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id) {
- Point *a;
+ Point *a = nullptr;
bool from_exists = points.lookup(p_from_id, a);
ERR_FAIL_COND_V_MSG(!from_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id));
- Point *b;
+ Point *b = nullptr;
bool to_exists = points.lookup(p_to_id, b);
ERR_FAIL_COND_V_MSG(!to_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id));
@@ -513,7 +513,7 @@ Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id) {
}
void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set if point is disabled. Point with id: %d doesn't exist.", p_id));
@@ -521,7 +521,7 @@ void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) {
}
bool AStar3D::is_point_disabled(int64_t p_id) const {
- Point *p;
+ Point *p = nullptr;
bool p_exists = points.lookup(p_id, p);
ERR_FAIL_COND_V_MSG(!p_exists, false, vformat("Can't get if point is disabled. Point with id: %d doesn't exist.", p_id));
@@ -660,11 +660,11 @@ real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
return scost;
}
- AStar3D::Point *from_point;
+ AStar3D::Point *from_point = nullptr;
bool from_exists = astar.points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *to_point;
+ AStar3D::Point *to_point = nullptr;
bool to_exists = astar.points.lookup(p_to_id, to_point);
ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
@@ -677,11 +677,11 @@ real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
return scost;
}
- AStar3D::Point *from_point;
+ AStar3D::Point *from_point = nullptr;
bool from_exists = astar.points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *to_point;
+ AStar3D::Point *to_point = nullptr;
bool to_exists = astar.points.lookup(p_to_id, to_point);
ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
@@ -689,11 +689,11 @@ real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
}
Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id) {
- AStar3D::Point *a;
+ AStar3D::Point *a = nullptr;
bool from_exists = astar.points.lookup(p_from_id, a);
ERR_FAIL_COND_V_MSG(!from_exists, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *b;
+ AStar3D::Point *b = nullptr;
bool to_exists = astar.points.lookup(p_to_id, b);
ERR_FAIL_COND_V_MSG(!to_exists, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id));
@@ -737,11 +737,11 @@ Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id) {
}
Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id) {
- AStar3D::Point *a;
+ AStar3D::Point *a = nullptr;
bool from_exists = astar.points.lookup(p_from_id, a);
ERR_FAIL_COND_V_MSG(!from_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *b;
+ AStar3D::Point *b = nullptr;
bool to_exists = astar.points.lookup(p_to_id, b);
ERR_FAIL_COND_V_MSG(!to_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id));
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 379d34aa2a..d17f465ab8 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -106,16 +106,45 @@ Size2 AStarGrid2D::get_cell_size() const {
return cell_size;
}
+void AStarGrid2D::set_cell_shape(CellShape p_cell_shape) {
+ if (cell_shape == p_cell_shape) {
+ return;
+ }
+
+ ERR_FAIL_INDEX(p_cell_shape, CellShape::CELL_SHAPE_MAX);
+ cell_shape = p_cell_shape;
+ dirty = true;
+}
+
+AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const {
+ return cell_shape;
+}
+
void AStarGrid2D::update() {
points.clear();
const int32_t end_x = region.get_end().x;
const int32_t end_y = region.get_end().y;
+ const Vector2 half_cell_size = cell_size / 2;
for (int32_t y = region.position.y; y < end_y; y++) {
LocalVector<Point> line;
for (int32_t x = region.position.x; x < end_x; x++) {
- line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size));
+ Vector2 v = offset;
+ switch (cell_shape) {
+ case CELL_SHAPE_ISOMETRIC_RIGHT:
+ v += half_cell_size + Vector2(x + y, y - x) * half_cell_size;
+ break;
+ case CELL_SHAPE_ISOMETRIC_DOWN:
+ v += half_cell_size + Vector2(x - y, x + y) * half_cell_size;
+ break;
+ case CELL_SHAPE_SQUARE:
+ v += Vector2(x, y) * cell_size;
+ break;
+ default:
+ break;
+ }
+ line.push_back(Point(Vector2i(x, y), v));
}
points.push_back(line);
}
@@ -620,6 +649,8 @@ void AStarGrid2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_offset"), &AStarGrid2D::get_offset);
ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &AStarGrid2D::set_cell_size);
ClassDB::bind_method(D_METHOD("get_cell_size"), &AStarGrid2D::get_cell_size);
+ ClassDB::bind_method(D_METHOD("set_cell_shape", "cell_shape"), &AStarGrid2D::set_cell_shape);
+ ClassDB::bind_method(D_METHOD("get_cell_shape"), &AStarGrid2D::get_cell_shape);
ClassDB::bind_method(D_METHOD("is_in_bounds", "x", "y"), &AStarGrid2D::is_in_bounds);
ClassDB::bind_method(D_METHOD("is_in_boundsv", "id"), &AStarGrid2D::is_in_boundsv);
ClassDB::bind_method(D_METHOD("is_dirty"), &AStarGrid2D::is_dirty);
@@ -651,6 +682,7 @@ void AStarGrid2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size"), "set_cell_size", "get_cell_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_shape", PROPERTY_HINT_ENUM, "Square,IsometricRight,IsometricDown"), "set_cell_shape", "get_cell_shape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "jumping_enabled"), "set_jumping_enabled", "is_jumping_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "default_compute_heuristic", PROPERTY_HINT_ENUM, "Euclidean,Manhattan,Octile,Chebyshev"), "set_default_compute_heuristic", "get_default_compute_heuristic");
@@ -668,4 +700,9 @@ void AStarGrid2D::_bind_methods() {
BIND_ENUM_CONSTANT(DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE);
BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES);
BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX);
+
+ BIND_ENUM_CONSTANT(CELL_SHAPE_SQUARE);
+ BIND_ENUM_CONSTANT(CELL_SHAPE_ISOMETRIC_RIGHT);
+ BIND_ENUM_CONSTANT(CELL_SHAPE_ISOMETRIC_DOWN);
+ BIND_ENUM_CONSTANT(CELL_SHAPE_MAX);
}
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index 619551b754..69cb77dd3e 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -56,11 +56,19 @@ public:
HEURISTIC_MAX,
};
+ enum CellShape {
+ CELL_SHAPE_SQUARE,
+ CELL_SHAPE_ISOMETRIC_RIGHT,
+ CELL_SHAPE_ISOMETRIC_DOWN,
+ CELL_SHAPE_MAX,
+ };
+
private:
Rect2i region;
Vector2 offset;
Size2 cell_size = Size2(1, 1);
bool dirty = false;
+ CellShape cell_shape = CELL_SHAPE_SQUARE;
bool jumping_enabled = false;
DiagonalMode diagonal_mode = DIAGONAL_MODE_ALWAYS;
@@ -157,6 +165,9 @@ public:
void set_cell_size(const Size2 &p_cell_size);
Size2 get_cell_size() const;
+ void set_cell_shape(CellShape p_cell_shape);
+ CellShape get_cell_shape() const;
+
void update();
bool is_in_bounds(int32_t p_x, int32_t p_y) const;
@@ -193,5 +204,6 @@ public:
VARIANT_ENUM_CAST(AStarGrid2D::DiagonalMode);
VARIANT_ENUM_CAST(AStarGrid2D::Heuristic);
+VARIANT_ENUM_CAST(AStarGrid2D::CellShape)
#endif // A_STAR_GRID_2D_H
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 859810df37..cea845bf7c 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -200,11 +200,11 @@ inline bool AABB::encloses(const AABB &p_aabb) const {
return (
(src_min.x <= dst_min.x) &&
- (src_max.x > dst_max.x) &&
+ (src_max.x >= dst_max.x) &&
(src_min.y <= dst_min.y) &&
- (src_max.y > dst_max.y) &&
+ (src_max.y >= dst_max.y) &&
(src_min.z <= dst_min.z) &&
- (src_max.z > dst_max.z));
+ (src_max.z >= dst_max.z));
}
Vector3 AABB::get_support(const Vector3 &p_normal) const {
diff --git a/core/math/basis.h b/core/math/basis.h
index b4d971464e..e3094114e8 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -136,6 +136,8 @@ struct _NO_DISCARD_ Basis {
_FORCE_INLINE_ Basis operator-(const Basis &p_matrix) const;
_FORCE_INLINE_ void operator*=(const real_t p_val);
_FORCE_INLINE_ Basis operator*(const real_t p_val) const;
+ _FORCE_INLINE_ void operator/=(const real_t p_val);
+ _FORCE_INLINE_ Basis operator/(const real_t p_val) const;
bool is_orthogonal() const;
bool is_orthonormal() const;
@@ -289,6 +291,18 @@ _FORCE_INLINE_ Basis Basis::operator*(const real_t p_val) const {
return ret;
}
+_FORCE_INLINE_ void Basis::operator/=(const real_t p_val) {
+ rows[0] /= p_val;
+ rows[1] /= p_val;
+ rows[2] /= p_val;
+}
+
+_FORCE_INLINE_ Basis Basis::operator/(const real_t p_val) const {
+ Basis ret(*this);
+ ret /= p_val;
+ return ret;
+}
+
Vector3 Basis::xform(const Vector3 &p_vector) const {
return Vector3(
rows[0].dot(p_vector),
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index b37fce9e9c..9907d579a5 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -119,6 +119,10 @@ public:
}
}
+ static real_t get_distance_to_segment(const Vector2 &p_point, const Vector2 *p_segment) {
+ return p_point.distance_to(get_closest_point_to_segment(p_point, p_segment));
+ }
+
static bool is_point_in_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) {
Vector2 an = a - s;
Vector2 bn = b - s;
@@ -249,6 +253,28 @@ public:
return -1;
}
+ static bool segment_intersects_rect(const Vector2 &p_from, const Vector2 &p_to, const Rect2 &p_rect) {
+ if (p_rect.has_point(p_from) || p_rect.has_point(p_to)) {
+ return true;
+ }
+
+ const Vector2 rect_points[4] = {
+ p_rect.position,
+ p_rect.position + Vector2(p_rect.size.x, 0),
+ p_rect.position + p_rect.size,
+ p_rect.position + Vector2(0, p_rect.size.y)
+ };
+
+ // Check if any of the rect's edges intersect the segment.
+ for (int i = 0; i < 4; i++) {
+ if (segment_intersects_segment(p_from, p_to, rect_points[i], rect_points[(i + 1) % 4], nullptr)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
enum PolyBooleanOperation {
OPERATION_UNION,
OPERATION_DIFFERENCE,
diff --git a/core/math/geometry_3d.h b/core/math/geometry_3d.h
index 99c554fe05..305a64e39c 100644
--- a/core/math/geometry_3d.h
+++ b/core/math/geometry_3d.h
@@ -31,6 +31,7 @@
#ifndef GEOMETRY_3D_H
#define GEOMETRY_3D_H
+#include "core/math/delaunay_3d.h"
#include "core/math/face3.h"
#include "core/object/object.h"
#include "core/templates/local_vector.h"
@@ -532,6 +533,21 @@ public:
return clipped;
}
+ static Vector<int32_t> tetrahedralize_delaunay(const Vector<Vector3> &p_points) {
+ Vector<Delaunay3D::OutputSimplex> tetr = Delaunay3D::tetrahedralize(p_points);
+ Vector<int32_t> tetrahedrons;
+
+ tetrahedrons.resize(4 * tetr.size());
+ int32_t *ptr = tetrahedrons.ptrw();
+ for (int i = 0; i < tetr.size(); i++) {
+ *ptr++ = tetr[i].points[0];
+ *ptr++ = tetr[i].points[1];
+ *ptr++ = tetr[i].points[2];
+ *ptr++ = tetr[i].points[3];
+ }
+ return tetrahedrons;
+ }
+
// Create a "wrap" that encloses the given geometry.
static Vector<Face3> wrap_geometry(Vector<Face3> p_array, real_t *p_error = nullptr);
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 366ccca4cb..3060f31970 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -198,6 +198,22 @@ public:
#endif
}
+ // These methods assume (p_num + p_den) doesn't overflow.
+ static _ALWAYS_INLINE_ int32_t division_round_up(int32_t p_num, int32_t p_den) {
+ int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1;
+ return (p_num + p_den + offset) / p_den;
+ }
+ static _ALWAYS_INLINE_ uint32_t division_round_up(uint32_t p_num, uint32_t p_den) {
+ return (p_num + p_den - 1) / p_den;
+ }
+ static _ALWAYS_INLINE_ int64_t division_round_up(int64_t p_num, int64_t p_den) {
+ int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1;
+ return (p_num + p_den + offset) / p_den;
+ }
+ static _ALWAYS_INLINE_ uint64_t division_round_up(uint64_t p_num, uint64_t p_den) {
+ return (p_num + p_den - 1) / p_den;
+ }
+
static _ALWAYS_INLINE_ bool is_finite(double p_val) { return isfinite(p_val); }
static _ALWAYS_INLINE_ bool is_finite(float p_val) { return isfinite(p_val); }
diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp
index bc4682fd90..a22d075b64 100644
--- a/core/math/transform_2d.cpp
+++ b/core/math/transform_2d.cpp
@@ -295,6 +295,18 @@ Transform2D Transform2D::operator*(const real_t p_val) const {
return ret;
}
+void Transform2D::operator/=(const real_t p_val) {
+ columns[0] /= p_val;
+ columns[1] /= p_val;
+ columns[2] /= p_val;
+}
+
+Transform2D Transform2D::operator/(const real_t p_val) const {
+ Transform2D ret(*this);
+ ret /= p_val;
+ return ret;
+}
+
Transform2D::operator String() const {
return "[X: " + columns[0].operator String() +
", Y: " + columns[1].operator String() +
diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h
index dd1a33c5d5..9ff925f66f 100644
--- a/core/math/transform_2d.h
+++ b/core/math/transform_2d.h
@@ -109,6 +109,8 @@ struct _NO_DISCARD_ Transform2D {
Transform2D operator*(const Transform2D &p_transform) const;
void operator*=(const real_t p_val);
Transform2D operator*(const real_t p_val) const;
+ void operator/=(const real_t p_val);
+ Transform2D operator/(const real_t p_val) const;
Transform2D interpolate_with(const Transform2D &p_transform, const real_t p_c) const;
diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp
index cdc94676c9..20713349d7 100644
--- a/core/math/transform_3d.cpp
+++ b/core/math/transform_3d.cpp
@@ -208,6 +208,17 @@ Transform3D Transform3D::operator*(const real_t p_val) const {
return ret;
}
+void Transform3D::operator/=(const real_t p_val) {
+ basis /= p_val;
+ origin /= p_val;
+}
+
+Transform3D Transform3D::operator/(const real_t p_val) const {
+ Transform3D ret(*this);
+ ret /= p_val;
+ return ret;
+}
+
Transform3D::operator String() const {
return "[X: " + basis.get_column(0).operator String() +
", Y: " + basis.get_column(1).operator String() +
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index 70141a3dbe..d1ec34d53f 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -104,6 +104,8 @@ struct _NO_DISCARD_ Transform3D {
Transform3D operator*(const Transform3D &p_transform) const;
void operator*=(const real_t p_val);
Transform3D operator*(const real_t p_val) const;
+ void operator/=(const real_t p_val);
+ Transform3D operator/(const real_t p_val) const;
Transform3D interpolate_with(const Transform3D &p_transform, real_t p_c) const;
diff --git a/core/math/vector2i.h b/core/math/vector2i.h
index e6850347c3..b2c75beb4d 100644
--- a/core/math/vector2i.h
+++ b/core/math/vector2i.h
@@ -85,6 +85,14 @@ struct _NO_DISCARD_ Vector2i {
return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y));
}
+ double distance_to(const Vector2i &p_to) const {
+ return (p_to - *this).length();
+ }
+
+ int64_t distance_squared_to(const Vector2i &p_to) const {
+ return (p_to - *this).length_squared();
+ }
+
Vector2i operator+(const Vector2i &p_v) const;
void operator+=(const Vector2i &p_v);
Vector2i operator-(const Vector2i &p_v) const;
diff --git a/core/math/vector3i.h b/core/math/vector3i.h
index 53d3829a99..5a5e9deda8 100644
--- a/core/math/vector3i.h
+++ b/core/math/vector3i.h
@@ -87,6 +87,9 @@ struct _NO_DISCARD_ Vector3i {
Vector3i clamp(const Vector3i &p_min, const Vector3i &p_max) const;
Vector3i snapped(const Vector3i &p_step) const;
+ _FORCE_INLINE_ double distance_to(const Vector3i &p_to) const;
+ _FORCE_INLINE_ int64_t distance_squared_to(const Vector3i &p_to) const;
+
/* Operators */
_FORCE_INLINE_ Vector3i &operator+=(const Vector3i &p_v);
@@ -143,6 +146,14 @@ Vector3i Vector3i::sign() const {
return Vector3i(SIGN(x), SIGN(y), SIGN(z));
}
+double Vector3i::distance_to(const Vector3i &p_to) const {
+ return (p_to - *this).length();
+}
+
+int64_t Vector3i::distance_squared_to(const Vector3i &p_to) const {
+ return (p_to - *this).length_squared();
+}
+
/* Operators */
Vector3i &Vector3i::operator+=(const Vector3i &p_v) {
diff --git a/core/math/vector4i.h b/core/math/vector4i.h
index b815aa8e76..7d85d473d9 100644
--- a/core/math/vector4i.h
+++ b/core/math/vector4i.h
@@ -84,6 +84,9 @@ struct _NO_DISCARD_ Vector4i {
_FORCE_INLINE_ void zero();
+ _FORCE_INLINE_ double distance_to(const Vector4i &p_to) const;
+ _FORCE_INLINE_ int64_t distance_squared_to(const Vector4i &p_to) const;
+
_FORCE_INLINE_ Vector4i abs() const;
_FORCE_INLINE_ Vector4i sign() const;
Vector4i clamp(const Vector4i &p_min, const Vector4i &p_max) const;
@@ -139,6 +142,14 @@ double Vector4i::length() const {
return Math::sqrt((double)length_squared());
}
+double Vector4i::distance_to(const Vector4i &p_to) const {
+ return (p_to - *this).length();
+}
+
+int64_t Vector4i::distance_squared_to(const Vector4i &p_to) const {
+ return (p_to - *this).length_squared();
+}
+
Vector4i Vector4i::abs() const {
return Vector4i(Math::abs(x), Math::abs(y), Math::abs(z), Math::abs(w));
}
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index bf1bd0de93..45fbb19f88 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -31,6 +31,7 @@
#include "class_db.h"
#include "core/config/engine.h"
+#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/mutex.h"
@@ -1299,6 +1300,12 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia
check = check->inherits_ptr;
}
+ // The "free()" method is special, so we assume it exists and return a Callable.
+ if (p_property == CoreStringNames::get_singleton()->_free) {
+ r_value = Callable(p_object, p_property);
+ return true;
+ }
+
return false;
}
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 5a776e2106..3901c4835d 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -728,7 +728,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
r_error.expected = 0;
return Variant();
}
- if (Object::cast_to<RefCounted>(this)) {
+ if (is_ref_counted()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference.");
}
diff --git a/core/object/object.h b/core/object/object.h
index fdd1c0b267..d697f14b7e 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -235,7 +235,7 @@ struct MethodInfo {
return arguments_metadata.size() > p_arg ? arguments_metadata[p_arg] : 0;
}
- inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; }
+ inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id && name == p_method.name; }
inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); }
operator Dictionary() const;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 69da50f074..bb714d5bc3 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -243,7 +243,7 @@ public:
virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const = 0;
virtual void get_string_delimiters(List<String> *p_delimiters) const = 0;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); }
- virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) { return Vector<ScriptTemplate>(); }
+ virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) { return Vector<ScriptTemplate>(); }
virtual bool is_using_templates() { return false; }
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const = 0;
virtual String validate_path(const String &p_path) const { return ""; }
@@ -371,6 +371,7 @@ public:
virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); }
virtual void reload_all_scripts() = 0;
+ virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) = 0;
virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) = 0;
/* LOADER FUNCTIONS */
diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp
index 79f39cb626..be62cabe25 100644
--- a/core/object/script_language_extension.cpp
+++ b/core/object/script_language_extension.cpp
@@ -107,7 +107,7 @@ void ScriptLanguageExtension::_bind_methods() {
GDVIRTUAL_BIND(_supports_builtin_mode);
GDVIRTUAL_BIND(_supports_documentation);
GDVIRTUAL_BIND(_can_inherit_from_file);
- GDVIRTUAL_BIND(_find_function, "class_name", "function_name");
+ GDVIRTUAL_BIND(_find_function, "function", "code");
GDVIRTUAL_BIND(_make_function, "class_name", "function_name", "function_args");
GDVIRTUAL_BIND(_open_in_external_editor, "script", "line", "column");
GDVIRTUAL_BIND(_overrides_external_editor);
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index 852b2aebd8..5b10739486 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -265,7 +265,7 @@ public:
GDVIRTUAL1RC(TypedArray<Dictionary>, _get_built_in_templates, StringName)
- virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override {
+ virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override {
TypedArray<Dictionary> ret;
GDVIRTUAL_REQUIRED_CALL(_get_built_in_templates, p_object, ret);
Vector<ScriptTemplate> stret;
@@ -562,6 +562,7 @@ public:
}
EXBIND0(reload_all_scripts)
+ EXBIND2(reload_scripts, const Array &, bool)
EXBIND2(reload_tool_script, const Ref<Script> &, bool)
/* LOADER FUNCTIONS */
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index 631767219f..e2ab473b01 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -33,6 +33,7 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/os/thread_safe.h"
+#include "core/templates/command_queue_mt.h"
void WorkerThreadPool::Task::free_template_userdata() {
ERR_FAIL_NULL(template_userdata);
@@ -43,24 +44,18 @@ void WorkerThreadPool::Task::free_template_userdata() {
WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
-void WorkerThreadPool::_process_task_queue() {
- task_mutex.lock();
- Task *task = task_queue.first()->self();
- task_queue.remove(task_queue.first());
- task_mutex.unlock();
- _process_task(task);
-}
+thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr;
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.
+#ifdef THREADS_ENABLED
+ int pool_thread_index = thread_ids[Thread::get_caller_id()];
+ ThreadData &curr_thread = threads[pool_thread_index];
+ Task *prev_task = nullptr; // In case this is recursively called.
+ bool safe_for_nodes_backup = is_current_thread_safe_for_nodes();
- if (!use_native_low_priority_threads) {
+ {
// Tasks must start with this unset. They are free to set-and-forget otherwise.
set_current_thread_safe_for_nodes(false);
- pool_thread_index = thread_ids[Thread::get_caller_id()];
- ThreadData &curr_thread = threads[pool_thread_index];
// Since the WorkerThreadPool is started before the script server,
// its pre-created threads can't have ScriptServer::thread_enter() called on them early.
// Therefore, we do it late at the first opportunity, so in case the task
@@ -71,15 +66,11 @@ void WorkerThreadPool::_process_task(Task *p_task) {
}
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;
- }
+ prev_task = curr_thread.current_task;
+ curr_thread.current_task = p_task;
task_mutex.unlock();
}
+#endif
if (p_task->group) {
// Handling a group
@@ -111,33 +102,24 @@ void WorkerThreadPool::_process_task(Task *p_task) {
memdelete(p_task->template_userdata); // This is no longer needed at this point, so get rid of it.
}
- if (low_priority && use_native_low_priority_threads) {
- p_task->completed = true;
- p_task->done_semaphore.post();
- if (do_post) {
- p_task->group->completed.set_to(true);
- }
- } else {
- if (do_post) {
- p_task->group->done_semaphore.post();
- p_task->group->completed.set_to(true);
- }
- uint32_t max_users = p_task->group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
- uint32_t finished_users = p_task->group->finished.increment();
-
- if (finished_users == max_users) {
- // Get rid of the group, because nobody else is using it.
- task_mutex.lock();
- group_allocator.free(p_task->group);
- task_mutex.unlock();
- }
-
- // For groups, tasks get rid of themselves.
+ if (do_post) {
+ p_task->group->done_semaphore.post();
+ p_task->group->completed.set_to(true);
+ }
+ uint32_t max_users = p_task->group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
+ uint32_t finished_users = p_task->group->finished.increment();
+ if (finished_users == max_users) {
+ // Get rid of the group, because nobody else is using it.
task_mutex.lock();
- task_allocator.free(p_task);
+ group_allocator.free(p_task->group);
task_mutex.unlock();
}
+
+ // For groups, tasks get rid of themselves.
+
+ task_mutex.lock();
+ task_allocator.free(p_task);
} else {
if (p_task->native_func) {
p_task->native_func(p_task->native_func_userdata);
@@ -150,88 +132,164 @@ void WorkerThreadPool::_process_task(Task *p_task) {
task_mutex.lock();
p_task->completed = true;
- for (uint8_t i = 0; i < p_task->waiting; i++) {
- p_task->done_semaphore.post();
+ p_task->pool_thread_index = -1;
+ if (p_task->waiting_user) {
+ p_task->done_semaphore.post(p_task->waiting_user);
}
- if (!use_native_low_priority_threads) {
- p_task->pool_thread_index = -1;
+ // Let awaiters know.
+ for (uint32_t i = 0; i < threads.size(); i++) {
+ if (threads[i].awaited_task == p_task) {
+ threads[i].cond_var.notify_one();
+ threads[i].signaled = true;
+ }
}
- task_mutex.unlock(); // Keep mutex down to here since on unlock the task may be freed.
}
- // 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();
- ThreadData &curr_thread = threads[pool_thread_index];
- curr_thread.current_low_prio_task = prev_low_prio_task;
- if (low_priority) {
+#ifdef THREADS_ENABLED
+ {
+ curr_thread.current_task = prev_task;
+ if (p_task->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();
+ if (_try_promote_low_priority_task()) {
+ if (prev_task) { // Otherwise, this thread will catch it.
+ _notify_threads(&curr_thread, 1, 0);
+ }
}
}
+
task_mutex.unlock();
- if (post) {
- task_available_semaphore.post();
- }
}
+
+ set_current_thread_safe_for_nodes(safe_for_nodes_backup);
+#endif
}
void WorkerThreadPool::_thread_function(void *p_user) {
+ ThreadData *thread_data = (ThreadData *)p_user;
while (true) {
- singleton->task_available_semaphore.wait();
- if (singleton->exit_threads) {
- break;
+ Task *task_to_process = nullptr;
+ {
+ MutexLock lock(singleton->task_mutex);
+ if (singleton->exit_threads) {
+ return;
+ }
+ thread_data->signaled = false;
+
+ if (singleton->task_queue.first()) {
+ task_to_process = singleton->task_queue.first()->self();
+ singleton->task_queue.remove(singleton->task_queue.first());
+ } else {
+ thread_data->cond_var.wait(lock);
+ DEV_ASSERT(singleton->exit_threads || thread_data->signaled);
+ }
}
- singleton->_process_task_queue();
- }
-}
-void WorkerThreadPool::_native_low_priority_thread_function(void *p_user) {
- Task *task = (Task *)p_user;
- singleton->_process_task(task);
+ if (task_to_process) {
+ singleton->_process_task(task_to_process);
+ }
+ }
}
-void WorkerThreadPool::_post_task(Task *p_task, bool p_high_priority) {
+void WorkerThreadPool::_post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority) {
// Fall back to processing on the calling thread if there are no worker threads.
// Separated into its own variable to make it easier to extend this logic
// in custom builds.
bool process_on_calling_thread = threads.size() == 0;
if (process_on_calling_thread) {
- _process_task(p_task);
+ task_mutex.unlock();
+ for (uint32_t i = 0; i < p_count; i++) {
+ _process_task(p_tasks[i]);
+ }
return;
}
- task_mutex.lock();
- p_task->low_priority = !p_high_priority;
- if (!p_high_priority && use_native_low_priority_threads) {
- p_task->low_priority_thread = native_thread_allocator.alloc();
- task_mutex.unlock();
+ uint32_t to_process = 0;
+ uint32_t to_promote = 0;
+
+ ThreadData *caller_pool_thread = thread_ids.has(Thread::get_caller_id()) ? &threads[thread_ids[Thread::get_caller_id()]] : nullptr;
- if (p_task->group) {
- p_task->group->low_priority_native_tasks.push_back(p_task);
+ for (uint32_t i = 0; i < p_count; i++) {
+ p_tasks[i]->low_priority = !p_high_priority;
+ if (p_high_priority || low_priority_threads_used < max_low_priority_threads) {
+ task_queue.add_last(&p_tasks[i]->task_elem);
+ if (!p_high_priority) {
+ low_priority_threads_used++;
+ }
+ to_process++;
+ } else {
+ // Too many threads using low priority, must go to queue.
+ low_priority_task_queue.add_last(&p_tasks[i]->task_elem);
+ to_promote++;
}
- p_task->low_priority_thread->start(_native_low_priority_thread_function, p_task); // Pask task directly to thread.
- } else if (p_high_priority || low_priority_threads_used < max_low_priority_threads) {
- task_queue.add_last(&p_task->task_elem);
- if (!p_high_priority) {
- low_priority_threads_used++;
+ }
+
+ _notify_threads(caller_pool_thread, to_process, to_promote);
+
+ task_mutex.unlock();
+}
+
+void WorkerThreadPool::_notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count) {
+ uint32_t to_process = p_process_count;
+ uint32_t to_promote = p_promote_count;
+
+ // This is where which threads are awaken is decided according to the workload.
+ // Threads that will anyway have a chance to check the situation and process/promote tasks
+ // are excluded from being notified. Others will be tried anyway to try to distribute load.
+ // The current thread, if is a pool thread, is also excluded depending on the promoting/processing
+ // needs because it will anyway loop again. However, it will contribute to decreasing the count,
+ // which helps reducing sync traffic.
+
+ uint32_t thread_count = threads.size();
+
+ // First round:
+ // 1. For processing: notify threads that are not running tasks, to keep the stacks as shallow as possible.
+ // 2. For promoting: since it's exclusive with processing, we fin threads able to promote low-prio tasks now.
+ for (uint32_t i = 0;
+ i < thread_count && (to_process || to_promote);
+ i++, notify_index = (notify_index + 1) % thread_count) {
+ ThreadData &th = threads[notify_index];
+
+ if (th.signaled) {
+ continue;
+ }
+ if (th.current_task) {
+ // Good thread for promoting low-prio?
+ if (to_promote && th.awaited_task && th.current_task->low_priority) {
+ if (likely(&th != p_current_thread_data)) {
+ th.cond_var.notify_one();
+ }
+ th.signaled = true;
+ to_promote--;
+ }
+ } else {
+ if (to_process) {
+ if (likely(&th != p_current_thread_data)) {
+ th.cond_var.notify_one();
+ }
+ th.signaled = true;
+ to_process--;
+ }
+ }
+ }
+
+ // Second round:
+ // For processing: if the first round wasn't enough, let's try now with threads processing tasks but currently awaiting.
+ for (uint32_t i = 0;
+ i < thread_count && to_process;
+ i++, notify_index = (notify_index + 1) % thread_count) {
+ ThreadData &th = threads[notify_index];
+
+ if (th.signaled) {
+ continue;
+ }
+ if (th.awaited_task) {
+ if (likely(&th != p_current_thread_data)) {
+ th.cond_var.notify_one();
+ }
+ th.signaled = true;
+ to_process--;
}
- task_mutex.unlock();
- task_available_semaphore.post();
- } else {
- // Too many threads using low priority, must go to queue.
- low_priority_task_queue.add_last(&p_task->task_elem);
- task_mutex.unlock();
}
}
@@ -247,23 +305,6 @@ bool WorkerThreadPool::_try_promote_low_priority_task() {
}
}
-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);
}
@@ -273,15 +314,15 @@ WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable,
// Get a free task
Task *task = task_allocator.alloc();
TaskID id = last_task++;
+ task->self = id;
task->callable = p_callable;
task->native_func = p_func;
task->native_func_userdata = p_userdata;
task->description = p_description;
task->template_userdata = p_template_userdata;
tasks.insert(id, task);
- task_mutex.unlock();
- _post_task(task, p_high_priority);
+ _post_tasks_and_unlock(&task, 1, p_high_priority);
return id;
}
@@ -313,105 +354,117 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
}
Task *task = *taskp;
- 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;
- }
+ if (task->completed) {
+ if (task->waiting_pool == 0 && task->waiting_user == 0) {
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
}
+ task_mutex.unlock();
+ return OK;
+ }
+
+ ThreadData *caller_pool_thread = thread_ids.has(Thread::get_caller_id()) ? &threads[thread_ids[Thread::get_caller_id()]] : nullptr;
+ if (caller_pool_thread && p_task_id <= caller_pool_thread->current_task->self) {
+ // Deadlock prevention:
+ // When a pool thread wants to wait for an older task, the following situations can happen:
+ // 1. Awaited task is deep in the stack of the awaiter.
+ // 2. A group of awaiter threads end up depending on some tasks buried in the stack
+ // of their worker threads in such a way that progress can't be made.
+ // Both would entail a deadlock. Some may be handled here in the WorkerThreadPool
+ // with some extra logic and bookkeeping. However, there would still be unavoidable
+ // cases of deadlock because of the way waiting threads process outstanding tasks.
+ // Taking into account there's no feasible solution for every possible case
+ // with the current design, we just simply reject attempts to await on older tasks,
+ // with a specific error code that signals the situation so the caller can handle it.
+ task_mutex.unlock();
+ return ERR_BUSY;
+ }
+
+ if (caller_pool_thread) {
+ task->waiting_pool++;
+ } else {
+ task->waiting_user++;
+ }
- 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();
+ task_mutex.unlock();
+
+ if (caller_pool_thread) {
+ while (true) {
+ Task *task_to_process = nullptr;
+ {
+ MutexLock lock(task_mutex);
+ bool was_signaled = caller_pool_thread->signaled;
+ caller_pool_thread->signaled = false;
+
+ if (task->completed) {
+ // This thread was awaken also for some reason, but it's about to exit.
+ // Let's find out what may be pending and forward the requests.
+ if (!exit_threads && was_signaled) {
+ uint32_t to_process = task_queue.first() ? 1 : 0;
+ uint32_t to_promote = caller_pool_thread->current_task->low_priority && low_priority_task_queue.first() ? 1 : 0;
+ if (to_process || to_promote) {
+ // This thread must be left alone since it won't loop again.
+ caller_pool_thread->signaled = true;
+ _notify_threads(caller_pool_thread, to_process, to_promote);
+ }
}
+
+ task->waiting_pool--;
+ if (task->waiting_pool == 0 && task->waiting_user == 0) {
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
+ }
+
+ break;
}
- }
- }
- task_mutex.unlock();
+ if (!exit_threads) {
+ // This is a thread from the pool. It shouldn't just idle.
+ // Let's try to process other tasks while we wait.
- 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 (caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) {
+ if (_try_promote_low_priority_task()) {
+ _notify_threads(caller_pool_thread, 1, 0);
+ }
+ }
+
+ if (singleton->task_queue.first()) {
+ task_to_process = task_queue.first()->self();
+ task_queue.remove(task_queue.first());
}
- if (!must_exit) {
- if (task_available_semaphore.try_wait()) {
- if (exit_threads) {
- must_exit = true;
- } else {
- // Solve tasks while they are around.
- bool safe_for_nodes_backup = is_current_thread_safe_for_nodes();
- _process_task_queue();
- set_current_thread_safe_for_nodes(safe_for_nodes_backup);
- 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();
- }
+
+ if (!task_to_process) {
+ caller_pool_thread->awaited_task = task;
+
+ if (flushing_cmd_queue) {
+ flushing_cmd_queue->unlock();
}
+ caller_pool_thread->cond_var.wait(lock);
+ if (flushing_cmd_queue) {
+ flushing_cmd_queue->lock();
+ }
+
+ DEV_ASSERT(exit_threads || caller_pool_thread->signaled || task->completed);
+ caller_pool_thread->awaited_task = nullptr;
}
- 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();
}
- }
- task_mutex.lock();
- if (is_low_prio_waiting_for_another) {
- low_priority_tasks_awaiting_others--;
+ if (task_to_process) {
+ _process_task(task_to_process);
+ }
}
-
- 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);
+ } else {
+ task->done_semaphore.wait();
+ task_mutex.lock();
+ task->waiting_user--;
+ if (task->waiting_pool == 0 && task->waiting_user == 0) {
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
}
- tasks.erase(p_task_id);
- task_allocator.free(task);
+ task_mutex.unlock();
}
- task_mutex.unlock();
return OK;
}
@@ -455,11 +508,8 @@ WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_ca
}
groups[id] = group;
- task_mutex.unlock();
- for (int i = 0; i < p_tasks; i++) {
- _post_task(tasks_posted[i], p_high_priority);
- }
+ _post_tasks_and_unlock(tasks_posted, p_tasks, p_high_priority);
return id;
}
@@ -496,28 +546,24 @@ bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
}
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
+#ifdef THREADS_ENABLED
task_mutex.lock();
Group **groupp = groups.getptr(p_group);
task_mutex.unlock();
if (!groupp) {
ERR_FAIL_MSG("Invalid Group ID");
}
- Group *group = *groupp;
- if (group->low_priority_native_tasks.size() > 0) {
- for (Task *task : group->low_priority_native_tasks) {
- task->low_priority_thread->wait_to_finish();
- task_mutex.lock();
- native_thread_allocator.free(task->low_priority_thread);
- task_allocator.free(task);
- task_mutex.unlock();
- }
+ {
+ Group *group = *groupp;
- task_mutex.lock();
- group_allocator.free(group);
- task_mutex.unlock();
- } else {
+ if (flushing_cmd_queue) {
+ flushing_cmd_queue->unlock();
+ }
group->done_semaphore.wait();
+ if (flushing_cmd_queue) {
+ flushing_cmd_queue->lock();
+ }
uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later.
@@ -533,6 +579,7 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
groups.erase(p_group);
task_mutex.unlock();
+#endif
}
int WorkerThreadPool::get_thread_index() {
@@ -540,19 +587,23 @@ int WorkerThreadPool::get_thread_index() {
return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1;
}
-void WorkerThreadPool::init(int p_thread_count, bool p_use_native_threads_low_priority, float p_low_priority_task_ratio) {
+void WorkerThreadPool::thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue) {
+ ERR_FAIL_COND(flushing_cmd_queue != nullptr);
+ flushing_cmd_queue = p_queue;
+}
+
+void WorkerThreadPool::thread_exit_command_queue_mt_flush() {
+ ERR_FAIL_NULL(flushing_cmd_queue);
+ flushing_cmd_queue = nullptr;
+}
+
+void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) {
ERR_FAIL_COND(threads.size() > 0);
if (p_thread_count < 0) {
p_thread_count = OS::get_singleton()->get_default_thread_pool_size();
}
- 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 - 1);
- }
-
- use_native_low_priority_threads = p_use_native_threads_low_priority;
+ max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
threads.resize(p_thread_count);
@@ -568,24 +619,33 @@ void WorkerThreadPool::finish() {
return;
}
- task_mutex.lock();
- SelfList<Task> *E = low_priority_task_queue.first();
- while (E) {
- print_error("Task waiting was never re-claimed: " + E->self()->description);
- E = E->next();
+ {
+ MutexLock lock(task_mutex);
+ SelfList<Task> *E = low_priority_task_queue.first();
+ while (E) {
+ print_error("Task waiting was never re-claimed: " + E->self()->description);
+ E = E->next();
+ }
}
- task_mutex.unlock();
- exit_threads = true;
-
- for (uint32_t i = 0; i < threads.size(); i++) {
- task_available_semaphore.post();
+ {
+ MutexLock lock(task_mutex);
+ exit_threads = true;
+ }
+ for (ThreadData &data : threads) {
+ data.cond_var.notify_one();
}
-
for (ThreadData &data : threads) {
data.thread.wait_to_finish();
}
+ {
+ MutexLock lock(task_mutex);
+ for (KeyValue<TaskID, Task *> &E : tasks) {
+ task_allocator.free(E.value);
+ }
+ }
+
threads.clear();
}
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index dd56f95cae..c9921c808d 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -31,6 +31,7 @@
#ifndef WORKER_THREAD_POOL_H
#define WORKER_THREAD_POOL_H
+#include "core/os/condition_variable.h"
#include "core/os/memory.h"
#include "core/os/os.h"
#include "core/os/semaphore.h"
@@ -40,6 +41,8 @@
#include "core/templates/rid.h"
#include "core/templates/safe_refcount.h"
+class CommandQueueMT;
+
class WorkerThreadPool : public Object {
GDCLASS(WorkerThreadPool, Object)
public:
@@ -60,7 +63,7 @@ private:
};
struct Group {
- GroupID self;
+ GroupID self = -1;
SafeNumeric<uint32_t> index;
SafeNumeric<uint32_t> completed_index;
uint32_t max = 0;
@@ -68,23 +71,23 @@ private:
SafeFlag completed;
SafeNumeric<uint32_t> finished;
uint32_t tasks_used = 0;
- TightLocalVector<Task *> low_priority_native_tasks;
};
struct Task {
+ TaskID self = -1;
Callable callable;
void (*native_func)(void *) = nullptr;
void (*native_group_func)(void *, uint32_t) = nullptr;
void *native_func_userdata = nullptr;
String description;
- Semaphore done_semaphore;
+ Semaphore done_semaphore; // For user threads awaiting.
bool completed = false;
Group *group = nullptr;
SelfList<Task> task_elem;
- uint32_t waiting = 0;
+ uint32_t waiting_pool = 0;
+ uint32_t waiting_user = 0;
bool low_priority = false;
BaseTemplateUserdata *template_userdata = nullptr;
- Thread *low_priority_thread = nullptr;
int pool_thread_index = -1;
void free_template_userdata();
@@ -92,51 +95,65 @@ private:
task_elem(this) {}
};
- PagedAllocator<Task> task_allocator;
- PagedAllocator<Group> group_allocator;
- PagedAllocator<Thread> native_thread_allocator;
+ static const uint32_t TASKS_PAGE_SIZE = 1024;
+ static const uint32_t GROUPS_PAGE_SIZE = 256;
+
+ PagedAllocator<Task, false, TASKS_PAGE_SIZE> task_allocator;
+ PagedAllocator<Group, false, GROUPS_PAGE_SIZE> group_allocator;
SelfList<Task>::List low_priority_task_queue;
SelfList<Task>::List task_queue;
- Mutex task_mutex;
- Semaphore task_available_semaphore;
+ BinaryMutex task_mutex;
struct ThreadData {
- uint32_t index;
+ uint32_t index = 0;
Thread thread;
- Task *current_low_prio_task = nullptr;
bool ready_for_scripting = false;
+ bool signaled = false;
+ Task *current_task = nullptr;
+ Task *awaited_task = nullptr; // Null if not awaiting the condition variable. Special value for idle-waiting.
+ ConditionVariable cond_var;
};
TightLocalVector<ThreadData> threads;
bool exit_threads = false;
HashMap<Thread::ID, int> thread_ids;
- HashMap<TaskID, Task *> tasks;
- HashMap<GroupID, Group *> groups;
+ HashMap<
+ TaskID,
+ Task *,
+ HashMapHasherDefault,
+ HashMapComparatorDefault<TaskID>,
+ PagedAllocator<HashMapElement<TaskID, Task *>, false, TASKS_PAGE_SIZE>>
+ tasks;
+ HashMap<
+ GroupID,
+ Group *,
+ HashMapHasherDefault,
+ HashMapComparatorDefault<GroupID>,
+ PagedAllocator<HashMapElement<GroupID, Group *>, false, GROUPS_PAGE_SIZE>>
+ groups;
- 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;
+ uint32_t notify_index = 0; // For rotating across threads, no help distributing load.
uint64_t last_task = 1;
static void _thread_function(void *p_user);
- static void _native_low_priority_thread_function(void *p_user);
- void _process_task_queue();
void _process_task(Task *task);
- void _post_task(Task *p_task, bool p_high_priority);
+ void _post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority);
+ void _notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count);
bool _try_promote_low_priority_task();
- void _prevent_low_prio_saturation_deadlock();
static WorkerThreadPool *singleton;
+ static thread_local CommandQueueMT *flushing_cmd_queue;
+
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);
GroupID _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);
@@ -199,7 +216,10 @@ public:
static WorkerThreadPool *get_singleton() { return singleton; }
static int get_thread_index();
- void init(int p_thread_count = -1, bool p_use_native_threads_low_priority = true, float p_low_priority_task_ratio = 0.3);
+ static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue);
+ static void thread_exit_command_queue_mt_flush();
+
+ void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3);
void finish();
WorkerThreadPool();
~WorkerThreadPool();
diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h
index 6a6996019d..2b6b272e18 100644
--- a/core/os/condition_variable.h
+++ b/core/os/condition_variable.h
@@ -31,6 +31,10 @@
#ifndef CONDITION_VARIABLE_H
#define CONDITION_VARIABLE_H
+#include "core/os/mutex.h"
+
+#ifdef THREADS_ENABLED
+
#ifdef MINGW_ENABLED
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#include "thirdparty/mingw-std-threads/mingw.condition_variable.h"
@@ -64,4 +68,16 @@ public:
}
};
+#else // No threads.
+
+class ConditionVariable {
+public:
+ template <class BinaryMutexT>
+ void wait(const MutexLock<BinaryMutexT> &p_lock) const {}
+ void notify_one() const {}
+ void notify_all() const {}
+};
+
+#endif // THREADS_ENABLED
+
#endif // CONDITION_VARIABLE_H
diff --git a/core/os/keyboard.cpp b/core/os/keyboard.cpp
index 6078882839..973c216d15 100644
--- a/core/os/keyboard.cpp
+++ b/core/os/keyboard.cpp
@@ -410,7 +410,7 @@ Key find_keycode(const String &p_codestr) {
return keycode;
}
- String last_part = code_parts[code_parts.size() - 1];
+ const String &last_part = code_parts[code_parts.size() - 1];
const _KeyCodeText *kct = &_keycodes[0];
while (kct->text) {
@@ -422,7 +422,7 @@ Key find_keycode(const String &p_codestr) {
}
for (int part = 0; part < code_parts.size() - 1; part++) {
- String code_part = code_parts[part];
+ const String &code_part = code_parts[part];
if (code_part.nocasecmp_to(find_keycode_name(Key::SHIFT)) == 0) {
keycode |= KeyModifierMask::SHIFT;
} else if (code_part.nocasecmp_to(find_keycode_name(Key::CTRL)) == 0) {
diff --git a/core/os/mutex.cpp b/core/os/mutex.cpp
index 5d4e457c5f..9a8a2a2961 100644
--- a/core/os/mutex.cpp
+++ b/core/os/mutex.cpp
@@ -40,7 +40,11 @@ void _global_unlock() {
_global_mutex.unlock();
}
+#ifdef THREADS_ENABLED
+
template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>;
template class MutexImpl<THREADING_NAMESPACE::mutex>;
template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>;
template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>;
+
+#endif
diff --git a/core/os/mutex.h b/core/os/mutex.h
index 03af48ca7c..69f494d9cd 100644
--- a/core/os/mutex.h
+++ b/core/os/mutex.h
@@ -43,6 +43,8 @@
#define THREADING_NAMESPACE std
#endif
+#ifdef THREADS_ENABLED
+
template <class MutexT>
class MutexLock;
@@ -125,8 +127,8 @@ class MutexLock {
THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock;
public:
- _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) :
- lock(p_mutex.mutex){};
+ explicit MutexLock(const MutexT &p_mutex) :
+ lock(p_mutex.mutex) {}
};
// This specialization is needed so manual locking and MutexLock can be used
@@ -155,4 +157,38 @@ extern template class MutexImpl<THREADING_NAMESPACE::mutex>;
extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>;
extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>;
+#else // No threads.
+
+class MutexImpl {
+ mutable THREADING_NAMESPACE::mutex mutex;
+
+public:
+ void lock() const {}
+ void unlock() const {}
+ bool try_lock() const { return true; }
+};
+
+template <int Tag>
+class SafeBinaryMutex : public MutexImpl {
+ static thread_local uint32_t count;
+};
+
+template <class MutexT>
+class MutexLock {
+public:
+ MutexLock(const MutexT &p_mutex) {}
+};
+
+template <int Tag>
+class MutexLock<SafeBinaryMutex<Tag>> {
+public:
+ MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {}
+ ~MutexLock() {}
+};
+
+using Mutex = MutexImpl;
+using BinaryMutex = MutexImpl;
+
+#endif // THREADS_ENABLED
+
#endif // MUTEX_H
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 26ae286979..d5d9988cc1 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -504,6 +504,12 @@ bool OS::has_feature(const String &p_feature) {
}
#endif
+#ifdef THREADS_ENABLED
+ if (p_feature == "threads") {
+ return true;
+ }
+#endif
+
if (_check_internal_feature_support(p_feature)) {
return true;
}
diff --git a/core/os/semaphore.h b/core/os/semaphore.h
index 8bb1529bbd..19ef1dedc0 100644
--- a/core/os/semaphore.h
+++ b/core/os/semaphore.h
@@ -31,6 +31,10 @@
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
+#include <cstdint>
+
+#ifdef THREADS_ENABLED
+
#include "core/error/error_list.h"
#include "core/typedefs.h"
#ifdef DEBUG_ENABLED
@@ -58,10 +62,12 @@ private:
#endif
public:
- _ALWAYS_INLINE_ void post() const {
+ _ALWAYS_INLINE_ void post(uint32_t p_count = 1) const {
std::lock_guard lock(mutex);
- count++;
- condition.notify_one();
+ count += p_count;
+ for (uint32_t i = 0; i < p_count; ++i) {
+ condition.notify_one();
+ }
}
_ALWAYS_INLINE_ void wait() const {
@@ -130,4 +136,17 @@ public:
#endif
};
+#else // No threads.
+
+class Semaphore {
+public:
+ void post(uint32_t p_count = 1) const {}
+ void wait() const {}
+ bool try_wait() const {
+ return true;
+ }
+};
+
+#endif // THREADS_ENABLED
+
#endif // SEMAPHORE_H
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index 2ba90ba42c..afc74364f6 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -33,19 +33,22 @@
#include "thread.h"
+#ifdef THREADS_ENABLED
#include "core/object/script_language.h"
#include "core/templates/safe_refcount.h"
-Thread::PlatformFunctions Thread::platform_functions;
-
SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1.
thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID;
+#endif
+
+Thread::PlatformFunctions Thread::platform_functions;
void Thread::_set_platform_functions(const PlatformFunctions &p_functions) {
platform_functions = p_functions;
}
+#ifdef THREADS_ENABLED
void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) {
Thread::caller_id = p_caller_id;
if (platform_functions.set_priority) {
@@ -107,4 +110,6 @@ Thread::~Thread() {
}
}
+#endif // THREADS_ENABLED
+
#endif // PLATFORM_THREAD_OVERRIDE
diff --git a/core/os/thread.h b/core/os/thread.h
index cc954678f9..a0ecc24c91 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -53,6 +53,8 @@
class String;
+#ifdef THREADS_ENABLED
+
class Thread {
public:
typedef void (*Callback)(void *p_userdata);
@@ -86,6 +88,8 @@ public:
private:
friend class Main;
+ static PlatformFunctions platform_functions;
+
ID id = UNASSIGNED_ID;
static SafeNumeric<uint64_t> id_counter;
static thread_local ID caller_id;
@@ -93,8 +97,6 @@ private:
static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata);
- static PlatformFunctions platform_functions;
-
static void make_main_thread() { caller_id = MAIN_ID; }
static void release_main_thread() { caller_id = UNASSIGNED_ID; }
@@ -125,6 +127,64 @@ public:
~Thread();
};
+#else // No threads.
+
+class Thread {
+public:
+ typedef void (*Callback)(void *p_userdata);
+
+ typedef uint64_t ID;
+
+ enum : ID {
+ UNASSIGNED_ID = 0,
+ MAIN_ID = 1
+ };
+
+ enum Priority {
+ PRIORITY_LOW,
+ PRIORITY_NORMAL,
+ PRIORITY_HIGH
+ };
+
+ struct Settings {
+ Priority priority;
+ Settings() { priority = PRIORITY_NORMAL; }
+ };
+
+ struct PlatformFunctions {
+ Error (*set_name)(const String &) = nullptr;
+ void (*set_priority)(Thread::Priority) = nullptr;
+ void (*init)() = nullptr;
+ void (*wrapper)(Thread::Callback, void *) = nullptr;
+ void (*term)() = nullptr;
+ };
+
+private:
+ friend class Main;
+
+ static PlatformFunctions platform_functions;
+
+ static void make_main_thread() {}
+ static void release_main_thread() {}
+
+public:
+ static void _set_platform_functions(const PlatformFunctions &p_functions);
+
+ _FORCE_INLINE_ ID get_id() const { return 0; }
+ _FORCE_INLINE_ static ID get_caller_id() { return MAIN_ID; }
+ _FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
+
+ _FORCE_INLINE_ static bool is_main_thread() { return true; }
+
+ static Error set_name(const String &p_name) { return ERR_UNAVAILABLE; }
+
+ void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()) {}
+ bool is_started() const { return false; }
+ void wait_to_finish() {}
+};
+
+#endif // THREADS_ENABLED
+
#endif // THREAD_H
#endif // PLATFORM_THREAD_OVERRIDE
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 2785d1daa5..82b3d27942 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -307,7 +307,6 @@ void register_core_settings() {
GLOBAL_DEF(PropertyInfo(Variant::STRING, "network/tls/certificate_bundle_override", PROPERTY_HINT_FILE, "*.crt"), "");
GLOBAL_DEF("threading/worker_pool/max_threads", -1);
- GLOBAL_DEF("threading/worker_pool/use_system_threads_for_low_priority_tasks", true);
GLOBAL_DEF("threading/worker_pool/low_priority_thread_ratio", 0.3);
}
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 8fcf2b24b5..829c9bf777 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -881,7 +881,7 @@ StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) c
String TranslationServer::get_override_string(String &p_message) const {
String res;
- for (int i = 0; i < p_message.size(); i++) {
+ for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
@@ -895,7 +895,7 @@ String TranslationServer::get_override_string(String &p_message) const {
String TranslationServer::double_vowels(String &p_message) const {
String res;
- for (int i = 0; i < p_message.size(); i++) {
+ for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
@@ -913,7 +913,7 @@ String TranslationServer::double_vowels(String &p_message) const {
String TranslationServer::replace_with_accented_string(String &p_message) const {
String res;
- for (int i = 0; i < p_message.size(); i++) {
+ for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
@@ -936,7 +936,7 @@ String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const
char32_t fakebidisuffix = U'\u202c';
res += fakebidiprefix;
// The fake bidi unicode gets popped at every newline so pushing it back at every newline.
- for (int i = 0; i < p_message.size(); i++) {
+ for (int i = 0; i < p_message.length(); i++) {
if (p_message[i] == '\n') {
res += fakebidisuffix;
res += p_message[i];
@@ -978,7 +978,7 @@ const char32_t *TranslationServer::get_accented_version(char32_t p_character) co
}
bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_index < p_message.size() - 1 && p_message[p_index] == '%' &&
+ return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
(p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
}
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index a24cff4f11..6afe28a6a7 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1117,7 +1117,7 @@ String String::get_with_code_lines() const {
return ret;
}
-int String::get_slice_count(String p_splitter) const {
+int String::get_slice_count(const String &p_splitter) const {
if (is_empty()) {
return 0;
}
@@ -1136,7 +1136,7 @@ int String::get_slice_count(String p_splitter) const {
return slices;
}
-String String::get_slice(String p_splitter, int p_slice) const {
+String String::get_slice(const String &p_splitter, int p_slice) const {
if (is_empty() || p_splitter.is_empty()) {
return "";
}
@@ -3515,7 +3515,7 @@ bool String::matchn(const String &p_wildcard) const {
return _wildcard_match(p_wildcard.get_data(), get_data(), false);
}
-String String::format(const Variant &values, String placeholder) const {
+String String::format(const Variant &values, const String &placeholder) const {
String new_string = String(this->ptr());
if (values.get_type() == Variant::ARRAY) {
@@ -3961,27 +3961,42 @@ static int _humanize_digits(int p_num) {
}
String String::humanize_size(uint64_t p_size) {
+ int magnitude = 0;
uint64_t _div = 1;
- Vector<String> prefixes;
- prefixes.push_back(RTR("B"));
- prefixes.push_back(RTR("KiB"));
- prefixes.push_back(RTR("MiB"));
- prefixes.push_back(RTR("GiB"));
- prefixes.push_back(RTR("TiB"));
- prefixes.push_back(RTR("PiB"));
- prefixes.push_back(RTR("EiB"));
-
- int prefix_idx = 0;
-
- while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) {
+ while (p_size > _div * 1024 && magnitude < 6) {
_div *= 1024;
- prefix_idx++;
+ magnitude++;
}
- const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0;
- const double divisor = prefix_idx > 0 ? _div : 1;
+ if (magnitude == 0) {
+ return String::num(p_size) + " " + RTR("B");
+ } else {
+ String suffix;
+ switch (magnitude) {
+ case 1:
+ suffix = RTR("KiB");
+ break;
+ case 2:
+ suffix = RTR("MiB");
+ break;
+ case 3:
+ suffix = RTR("GiB");
+ break;
+ case 4:
+ suffix = RTR("TiB");
+ break;
+ case 5:
+ suffix = RTR("PiB");
+ break;
+ case 6:
+ suffix = RTR("EiB");
+ break;
+ }
- return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx];
+ const double divisor = _div;
+ const int digits = _humanize_digits(p_size / _div);
+ return String::num(p_size / divisor).pad_decimals(digits) + " " + suffix;
+ }
}
bool String::is_absolute_path() const {
@@ -4569,7 +4584,7 @@ bool String::is_valid_ip_address() const {
if (find(":") >= 0) {
Vector<String> ip = split(":");
for (int i = 0; i < ip.size(); i++) {
- String n = ip[i];
+ const String &n = ip[i];
if (n.is_empty()) {
continue;
}
@@ -4591,7 +4606,7 @@ bool String::is_valid_ip_address() const {
return false;
}
for (int i = 0; i < ip.size(); i++) {
- String n = ip[i];
+ const String &n = ip[i];
if (!n.is_valid_int()) {
return false;
}
@@ -5208,7 +5223,7 @@ String String::sprintf(const Array &values, bool *error) const {
return formatted;
}
-String String::quote(String quotechar) const {
+String String::quote(const String &quotechar) const {
return quotechar + *this + quotechar;
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 897b06fc6d..5ed20396d6 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -299,7 +299,7 @@ public:
bool is_quoted() const;
Vector<String> bigrams() const;
float similarity(const String &p_string) const;
- String format(const Variant &values, String placeholder = "{_}") const;
+ String format(const Variant &values, const String &placeholder = "{_}") const;
String replace_first(const String &p_key, const String &p_with) const;
String replace(const String &p_key, const String &p_with) const;
String replace(const char *p_key, const char *p_with) const;
@@ -315,7 +315,7 @@ public:
String lpad(int min_length, const String &character = " ") const;
String rpad(int min_length, const String &character = " ") const;
String sprintf(const Array &values, bool *error) const;
- String quote(String quotechar = "\"") const;
+ String quote(const String &quotechar = "\"") const;
String unquote() const;
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
@@ -349,8 +349,8 @@ public:
String to_snake_case() const;
String get_with_code_lines() const;
- int get_slice_count(String p_splitter) const;
- String get_slice(String p_splitter, int p_slice) const;
+ int get_slice_count(const String &p_splitter) const;
+ String get_slice(const String &p_splitter, int p_slice) const;
String get_slicec(char32_t p_splitter, int p_slice) const;
Vector<String> split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h
index 7e480653ac..b1010f7f43 100644
--- a/core/templates/command_queue_mt.h
+++ b/core/templates/command_queue_mt.h
@@ -31,6 +31,7 @@
#ifndef COMMAND_QUEUE_MT_H
#define COMMAND_QUEUE_MT_H
+#include "core/object/worker_thread_pool.h"
#include "core/os/memory.h"
#include "core/os/mutex.h"
#include "core/os/semaphore.h"
@@ -306,15 +307,15 @@ class CommandQueueMT {
struct CommandBase {
virtual void call() = 0;
- virtual void post() {}
- virtual ~CommandBase() {}
+ virtual SyncSemaphore *get_sync_semaphore() { return nullptr; }
+ virtual ~CommandBase() = default; // Won't be called.
};
struct SyncCommand : public CommandBase {
SyncSemaphore *sync_sem = nullptr;
- virtual void post() override {
- sync_sem->sem.post();
+ virtual SyncSemaphore *get_sync_semaphore() override {
+ return sync_sem;
}
};
@@ -340,6 +341,7 @@ class CommandQueueMT {
SyncSemaphore sync_sems[SYNC_SEMAPHORES];
Mutex mutex;
Semaphore *sync = nullptr;
+ uint64_t flush_read_ptr = 0;
template <class T>
T *allocate() {
@@ -362,31 +364,41 @@ class CommandQueueMT {
void _flush() {
lock();
- uint64_t read_ptr = 0;
- uint64_t limit = command_mem.size();
-
- while (read_ptr < limit) {
- uint64_t size = *(uint64_t *)&command_mem[read_ptr];
- read_ptr += 8;
- CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[read_ptr]);
-
- cmd->call(); //execute the function
- cmd->post(); //release in case it needs sync/ret
- cmd->~CommandBase(); //should be done, so erase the command
-
- read_ptr += size;
+ WorkerThreadPool::thread_enter_command_queue_mt_flush(this);
+ while (flush_read_ptr < command_mem.size()) {
+ uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr];
+ flush_read_ptr += 8;
+ CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
+
+ SyncSemaphore *sync_sem = cmd->get_sync_semaphore();
+ cmd->call();
+ if (sync_sem) {
+ sync_sem->sem.post(); // Release in case it needs sync/ret.
+ }
+
+ if (unlikely(flush_read_ptr == 0)) {
+ // A reentrant call flushed.
+ DEV_ASSERT(command_mem.is_empty());
+ unlock();
+ return;
+ }
+
+ flush_read_ptr += size;
}
+ WorkerThreadPool::thread_exit_command_queue_mt_flush();
command_mem.clear();
+ flush_read_ptr = 0;
unlock();
}
- void lock();
- void unlock();
void wait_for_flush();
SyncSemaphore *_alloc_sync_sem();
public:
+ void lock();
+ void unlock();
+
/* NORMAL PUSH COMMANDS */
DECL_PUSH(0)
SPACE_SEP_LIST(DECL_PUSH, 15)
diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h
index 6f3f78d4d2..d880eae0c3 100644
--- a/core/templates/paged_allocator.h
+++ b/core/templates/paged_allocator.h
@@ -40,7 +40,7 @@
#include <type_traits>
#include <typeinfo>
-template <class T, bool thread_safe = false>
+template <class T, bool thread_safe = false, uint32_t DEFAULT_PAGE_SIZE = 4096>
class PagedAllocator {
T **page_pool = nullptr;
T ***available_pool = nullptr;
@@ -53,10 +53,6 @@ class PagedAllocator {
SpinLock spin_lock;
public:
- enum {
- DEFAULT_PAGE_SIZE = 4096
- };
-
template <class... Args>
T *alloc(Args &&...p_args) {
if (thread_safe) {
diff --git a/core/templates/paged_array.h b/core/templates/paged_array.h
index 863e3eef11..69a792958a 100644
--- a/core/templates/paged_array.h
+++ b/core/templates/paged_array.h
@@ -53,7 +53,12 @@ class PagedArrayPool {
SpinLock spin_lock;
public:
- uint32_t alloc_page() {
+ struct PageInfo {
+ T *page = nullptr;
+ uint32_t page_id = 0;
+ };
+
+ PageInfo alloc_page() {
spin_lock.lock();
if (unlikely(pages_available == 0)) {
uint32_t pages_used = pages_allocated;
@@ -69,13 +74,11 @@ public:
}
pages_available--;
- uint32_t page = available_page_pool[pages_available];
+ uint32_t page_id = available_page_pool[pages_available];
+ T *page = page_pool[page_id];
spin_lock.unlock();
- return page;
- }
- T *get_page(uint32_t p_page_id) {
- return page_pool[p_page_id];
+ return PageInfo{ page, page_id };
}
void free_page(uint32_t p_page_id) {
@@ -190,9 +193,9 @@ public:
_grow_page_array(); //keep out of inline
}
- uint32_t page_id = page_pool->alloc_page();
- page_data[page_count] = page_pool->get_page(page_id);
- page_ids[page_count] = page_id;
+ typename PagedArrayPool<T>::PageInfo page_info = page_pool->alloc_page();
+ page_data[page_count] = page_info.page;
+ page_ids[page_count] = page_info.page_id;
}
// place the new value
diff --git a/core/templates/self_list.h b/core/templates/self_list.h
index fdf91beacc..afa0501c75 100644
--- a/core/templates/self_list.h
+++ b/core/templates/self_list.h
@@ -159,6 +159,9 @@ public:
_FORCE_INLINE_ SelfList<T> *first() { return _first; }
_FORCE_INLINE_ const SelfList<T> *first() const { return _first; }
+ // Forbid copying, which has broken behavior.
+ void operator=(const List &) = delete;
+
_FORCE_INLINE_ List() {}
_FORCE_INLINE_ ~List() {
// A self list must be empty on destruction.
@@ -185,6 +188,9 @@ public:
_FORCE_INLINE_ const SelfList<T> *prev() const { return _prev; }
_FORCE_INLINE_ T *self() const { return _self; }
+ // Forbid copying, which has broken behavior.
+ void operator=(const SelfList<T> &) = delete;
+
_FORCE_INLINE_ SelfList(T *p_self) {
_self = p_self;
}
diff --git a/core/typedefs.h b/core/typedefs.h
index 803b2e5ae0..8b04960b9a 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -233,6 +233,10 @@ constexpr T get_num_bits(T x) {
#define BSWAP16(x) __builtin_bswap16(x)
#define BSWAP32(x) __builtin_bswap32(x)
#define BSWAP64(x) __builtin_bswap64(x)
+#elif defined(_MSC_VER)
+#define BSWAP16(x) _byteswap_ushort(x)
+#define BSWAP32(x) _byteswap_ulong(x)
+#define BSWAP64(x) _byteswap_uint64(x)
#else
static inline uint16_t BSWAP16(uint16_t x) {
return (x >> 8) | (x << 8);
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 0b1174c873..55f687bdf9 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -31,13 +31,12 @@
#include "callable.h"
#include "callable_bind.h"
-#include "core/object/message_queue.h"
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
void Callable::call_deferredp(const Variant **p_arguments, int p_argcount) const {
- MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount);
+ MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount, true);
}
void Callable::callp(const Variant **p_arguments, int p_argcount, Variant &r_return_value, CallError &r_call_error) const {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 480f21df98..602d287f22 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -572,6 +572,7 @@ public:
static ValidatedBuiltInMethod get_validated_builtin_method(Variant::Type p_type, const StringName &p_method);
static PTRBuiltInMethod get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method);
+ static MethodInfo get_builtin_method_info(Variant::Type p_type, const StringName &p_method);
static int get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method);
static Variant::Type get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument);
static String get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument);
@@ -707,9 +708,20 @@ public:
bool has_key(const Variant &p_key, bool &r_valid) const;
/* Generic */
-
- void set(const Variant &p_index, const Variant &p_value, bool *r_valid = nullptr);
- Variant get(const Variant &p_index, bool *r_valid = nullptr) const;
+ enum VariantSetError {
+ SET_OK,
+ SET_KEYED_ERR,
+ SET_NAMED_ERR,
+ SET_INDEXED_ERR
+ };
+ enum VariantGetError {
+ GET_OK,
+ GET_KEYED_ERR,
+ GET_NAMED_ERR,
+ GET_INDEXED_ERR
+ };
+ void set(const Variant &p_index, const Variant &p_value, bool *r_valid = nullptr, VariantSetError *err_code = nullptr);
+ Variant get(const Variant &p_index, bool *r_valid = nullptr, VariantGetError *err_code = nullptr) const;
bool in(const Variant &p_index, bool *r_valid = nullptr) const;
bool iter_init(Variant &r_iter, bool &r_valid) const;
@@ -769,8 +781,8 @@ public:
static Variant get_constant_value(Variant::Type p_type, const StringName &p_value, bool *r_valid = nullptr);
static void get_enums_for_type(Variant::Type p_type, List<StringName> *p_enums);
- static void get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_name, List<StringName> *p_enumerations);
- static int get_enum_value(Variant::Type p_type, StringName p_enum_name, StringName p_enumeration, bool *r_valid = nullptr);
+ static void get_enumerations_for_enum(Variant::Type p_type, const StringName &p_enum_name, List<StringName> *p_enumerations);
+ static int get_enum_value(Variant::Type p_type, const StringName &p_enum_name, const StringName &p_enumeration, bool *r_valid = nullptr);
typedef String (*ObjectDeConstruct)(const Variant &p_object, void *ud);
typedef void (*ObjectConstruct)(const String &p_text, void *ud, Variant &r_value);
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 7121b4da96..03836985f3 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1071,14 +1071,14 @@ struct _VariantCall {
static ConstantData *constant_data;
- static void add_constant(int p_type, StringName p_constant_name, int64_t p_constant_value) {
+ static void add_constant(int p_type, const StringName &p_constant_name, int64_t p_constant_value) {
constant_data[p_type].value[p_constant_name] = p_constant_value;
#ifdef DEBUG_ENABLED
constant_data[p_type].value_ordered.push_back(p_constant_name);
#endif
}
- static void add_variant_constant(int p_type, StringName p_constant_name, const Variant &p_constant_value) {
+ static void add_variant_constant(int p_type, const StringName &p_constant_name, const Variant &p_constant_value) {
constant_data[p_type].variant_value[p_constant_name] = p_constant_value;
#ifdef DEBUG_ENABLED
constant_data[p_type].variant_value_ordered.push_back(p_constant_name);
@@ -1091,7 +1091,7 @@ struct _VariantCall {
static EnumData *enum_data;
- static void add_enum_constant(int p_type, StringName p_enum_type_name, StringName p_enumeration_name, int p_enum_value) {
+ static void add_enum_constant(int p_type, const StringName &p_enum_type_name, const StringName &p_enumeration_name, int p_enum_value) {
enum_data[p_type].value[p_enum_type_name][p_enumeration_name] = p_enum_value;
}
};
@@ -1114,6 +1114,46 @@ struct VariantBuiltInMethodInfo {
Variant::Type return_type;
int argument_count = 0;
Variant::Type (*get_argument_type)(int p_arg) = nullptr;
+
+ MethodInfo get_method_info(const StringName &p_name) const {
+ MethodInfo mi;
+ mi.name = p_name;
+
+ if (has_return_type) {
+ mi.return_val.type = return_type;
+ if (mi.return_val.type == Variant::NIL) {
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+ }
+
+ if (is_const) {
+ mi.flags |= METHOD_FLAG_CONST;
+ }
+ if (is_vararg) {
+ mi.flags |= METHOD_FLAG_VARARG;
+ }
+ if (is_static) {
+ mi.flags |= METHOD_FLAG_STATIC;
+ }
+
+ for (int i = 0; i < argument_count; i++) {
+ PropertyInfo pi;
+#ifdef DEBUG_METHODS_ENABLED
+ pi.name = argument_names[i];
+#else
+ pi.name = "arg" + itos(i + 1);
+#endif
+ pi.type = (*get_argument_type)(i);
+ if (pi.type == Variant::NIL) {
+ pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+ mi.arguments.push_back(pi);
+ }
+
+ mi.default_arguments = default_arguments;
+
+ return mi;
+ }
};
typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap;
@@ -1268,6 +1308,13 @@ Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type,
return method->ptrcall;
}
+MethodInfo Variant::get_builtin_method_info(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, MethodInfo());
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_NULL_V(method, MethodInfo());
+ return method->get_method_info(p_method);
+}
+
int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) {
ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0);
const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
@@ -1378,43 +1425,7 @@ void Variant::get_method_list(List<MethodInfo> *p_list) const {
for (const StringName &E : builtin_method_names[type]) {
const VariantBuiltInMethodInfo *method = builtin_method_info[type].lookup_ptr(E);
ERR_CONTINUE(!method);
-
- MethodInfo mi;
- mi.name = E;
-
- //return type
- if (method->has_return_type) {
- mi.return_val.type = method->return_type;
- if (mi.return_val.type == Variant::NIL) {
- mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- }
- }
-
- if (method->is_const) {
- mi.flags |= METHOD_FLAG_CONST;
- }
- if (method->is_vararg) {
- mi.flags |= METHOD_FLAG_VARARG;
- }
- if (method->is_static) {
- mi.flags |= METHOD_FLAG_STATIC;
- }
- for (int i = 0; i < method->argument_count; i++) {
- PropertyInfo pi;
-#ifdef DEBUG_METHODS_ENABLED
- pi.name = method->argument_names[i];
-#else
- pi.name = "arg" + itos(i + 1);
-#endif
- pi.type = method->get_argument_type(i);
- if (pi.type == Variant::NIL) {
- pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- }
- mi.arguments.push_back(pi);
- }
-
- mi.default_arguments = method->default_arguments;
- p_list->push_back(mi);
+ p_list->push_back(method->get_method_info(E));
}
}
}
@@ -1493,7 +1504,7 @@ void Variant::get_enums_for_type(Variant::Type p_type, List<StringName> *p_enums
}
}
-void Variant::get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_name, List<StringName> *p_enumerations) {
+void Variant::get_enumerations_for_enum(Variant::Type p_type, const StringName &p_enum_name, List<StringName> *p_enumerations) {
ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX);
_VariantCall::EnumData &enum_data = _VariantCall::enum_data[p_type];
@@ -1505,7 +1516,7 @@ void Variant::get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_
}
}
-int Variant::get_enum_value(Variant::Type p_type, StringName p_enum_name, StringName p_enumeration, bool *r_valid) {
+int Variant::get_enum_value(Variant::Type p_type, const StringName &p_enum_name, const StringName &p_enumeration, bool *r_valid) {
if (r_valid) {
*r_valid = false;
}
@@ -1796,6 +1807,8 @@ static void _register_variant_builtin_methods() {
bind_method(Vector2i, aspect, sarray(), varray());
bind_method(Vector2i, max_axis_index, sarray(), varray());
bind_method(Vector2i, min_axis_index, sarray(), varray());
+ bind_method(Vector2i, distance_to, sarray("to"), varray());
+ bind_method(Vector2i, distance_squared_to, sarray("to"), varray());
bind_method(Vector2i, length, sarray(), varray());
bind_method(Vector2i, length_squared, sarray(), varray());
bind_method(Vector2i, sign, sarray(), varray());
@@ -1886,6 +1899,8 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3i, min_axis_index, sarray(), varray());
bind_method(Vector3i, max_axis_index, sarray(), varray());
+ bind_method(Vector3i, distance_to, sarray("to"), varray());
+ bind_method(Vector3i, distance_squared_to, sarray("to"), varray());
bind_method(Vector3i, length, sarray(), varray());
bind_method(Vector3i, length_squared, sarray(), varray());
bind_method(Vector3i, sign, sarray(), varray());
@@ -1932,6 +1947,8 @@ static void _register_variant_builtin_methods() {
bind_method(Vector4i, abs, sarray(), varray());
bind_method(Vector4i, clamp, sarray("min", "max"), varray());
bind_method(Vector4i, snapped, sarray("step"), varray());
+ bind_method(Vector4i, distance_to, sarray("to"), varray());
+ bind_method(Vector4i, distance_squared_to, sarray("to"), varray());
/* Plane */
diff --git a/core/variant/variant_callable.cpp b/core/variant/variant_callable.cpp
new file mode 100644
index 0000000000..dc31b6d1ac
--- /dev/null
+++ b/core/variant/variant_callable.cpp
@@ -0,0 +1,81 @@
+/**************************************************************************/
+/* variant_callable.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "variant_callable.h"
+
+#include "core/templates/hashfuncs.h"
+
+bool VariantCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ return p_a->hash() == p_b->hash();
+}
+
+bool VariantCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ return p_a->hash() < p_b->hash();
+}
+
+uint32_t VariantCallable::hash() const {
+ return h;
+}
+
+String VariantCallable::get_as_text() const {
+ return vformat("%s::%s (Callable)", Variant::get_type_name(variant.get_type()), method);
+}
+
+CallableCustom::CompareEqualFunc VariantCallable::get_compare_equal_func() const {
+ return compare_equal;
+}
+
+CallableCustom::CompareLessFunc VariantCallable::get_compare_less_func() const {
+ return compare_less;
+}
+
+bool VariantCallable::is_valid() const {
+ return Variant::has_builtin_method(variant.get_type(), method);
+}
+
+StringName VariantCallable::get_method() const {
+ return method;
+}
+
+ObjectID VariantCallable::get_object() const {
+ return ObjectID();
+}
+
+void VariantCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ Variant v = variant;
+ v.callp(method, p_arguments, p_argcount, r_return_value, r_call_error);
+}
+
+VariantCallable::VariantCallable(const Variant &p_variant, const StringName &p_method) {
+ variant = p_variant;
+ method = p_method;
+ h = variant.hash();
+ h = hash_murmur3_one_64(Variant::get_builtin_method_hash(variant.get_type(), method), h);
+}
diff --git a/core/variant/variant_callable.h b/core/variant/variant_callable.h
new file mode 100644
index 0000000000..3f2b058aaf
--- /dev/null
+++ b/core/variant/variant_callable.h
@@ -0,0 +1,58 @@
+/**************************************************************************/
+/* variant_callable.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef VARIANT_CALLABLE_H
+#define VARIANT_CALLABLE_H
+
+#include "core/variant/callable.h"
+#include "core/variant/variant.h"
+
+class VariantCallable : public CallableCustom {
+ Variant variant;
+ StringName method;
+ uint32_t h = 0;
+
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+public:
+ uint32_t hash() const override;
+ String get_as_text() const override;
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
+ bool is_valid() const override;
+ StringName get_method() const override;
+ ObjectID get_object() const override;
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+ VariantCallable(const Variant &p_variant, const StringName &p_method);
+};
+
+#endif // VARIANT_CALLABLE_H
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index aed83ac010..4f9c38dc4c 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -412,6 +412,15 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorDivNZ<Vector4, Vector4i, double>>(Variant::OP_DIVIDE, Variant::VECTOR4I, Variant::FLOAT);
register_op<OperatorEvaluatorDivNZ<Vector4i, Vector4i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR4I, Variant::INT);
+ register_op<OperatorEvaluatorDiv<Transform2D, Transform2D, int64_t>>(Variant::OP_DIVIDE, Variant::TRANSFORM2D, Variant::INT);
+ register_op<OperatorEvaluatorDiv<Transform2D, Transform2D, double>>(Variant::OP_DIVIDE, Variant::TRANSFORM2D, Variant::FLOAT);
+
+ register_op<OperatorEvaluatorDiv<Transform3D, Transform3D, int64_t>>(Variant::OP_DIVIDE, Variant::TRANSFORM3D, Variant::INT);
+ register_op<OperatorEvaluatorDiv<Transform3D, Transform3D, double>>(Variant::OP_DIVIDE, Variant::TRANSFORM3D, Variant::FLOAT);
+
+ register_op<OperatorEvaluatorDiv<Basis, Basis, int64_t>>(Variant::OP_DIVIDE, Variant::BASIS, Variant::INT);
+ register_op<OperatorEvaluatorDiv<Basis, Basis, double>>(Variant::OP_DIVIDE, Variant::BASIS, Variant::FLOAT);
+
register_op<OperatorEvaluatorDiv<Quaternion, Quaternion, double>>(Variant::OP_DIVIDE, Variant::QUATERNION, Variant::FLOAT);
register_op<OperatorEvaluatorDiv<Quaternion, Quaternion, int64_t>>(Variant::OP_DIVIDE, Variant::QUATERNION, Variant::INT);
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 05f7abf32c..50c9c10987 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -30,6 +30,8 @@
#include "variant_setget.h"
+#include "variant_callable.h"
+
struct VariantSetterGetterInfo {
void (*setter)(Variant *base, const Variant *value, bool &valid);
void (*getter)(const Variant *base, Variant *value);
@@ -264,42 +266,45 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool
}
Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
- Variant ret;
uint32_t s = variant_setters_getters[type].size();
if (s) {
for (uint32_t i = 0; i < s; i++) {
if (variant_setters_getters_names[type][i] == p_member) {
+ Variant ret;
variant_setters_getters[type][i].getter(this, &ret);
r_valid = true;
return ret;
}
}
+ }
- r_valid = false;
-
- } else if (type == Variant::OBJECT) {
- Object *obj = get_validated_object();
- if (!obj) {
- r_valid = false;
- return "Instance base is null.";
- } else {
- return obj->get(p_member, &r_valid);
- }
- } else if (type == Variant::DICTIONARY) {
- const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member);
- if (v) {
- r_valid = true;
-
- return *v;
- } else {
- r_valid = false;
- }
-
- } else {
- r_valid = false;
+ switch (type) {
+ case Variant::OBJECT: {
+ Object *obj = get_validated_object();
+ if (!obj) {
+ r_valid = false;
+ return "Instance base is null.";
+ } else {
+ return obj->get(p_member, &r_valid);
+ }
+ } break;
+ case Variant::DICTIONARY: {
+ const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member);
+ if (v) {
+ r_valid = true;
+ return *v;
+ }
+ } break;
+ default: {
+ if (Variant::has_builtin_method(type, p_member)) {
+ r_valid = true;
+ return Callable(memnew(VariantCallable(*this, p_member)));
+ }
+ } break;
}
- return ret;
+ r_valid = false;
+ return Variant();
}
/**** INDEXED SETTERS AND GETTERS ****/
@@ -1166,30 +1171,48 @@ bool Variant::has_key(const Variant &p_key, bool &r_valid) const {
}
}
-void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid) {
+void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid, VariantSetError *err_code) {
+ if (err_code) {
+ *err_code = VariantSetError::SET_OK;
+ }
if (type == DICTIONARY || type == OBJECT) {
bool valid;
set_keyed(p_index, p_value, valid);
if (r_valid) {
*r_valid = valid;
+ if (!valid && err_code) {
+ *err_code = VariantSetError::SET_KEYED_ERR;
+ }
}
} else {
bool valid = false;
if (p_index.get_type() == STRING_NAME) {
set_named(*VariantGetInternalPtr<StringName>::get_ptr(&p_index), p_value, valid);
+ if (!valid && err_code) {
+ *err_code = VariantSetError::SET_NAMED_ERR;
+ }
} else if (p_index.get_type() == INT) {
bool obb;
set_indexed(*VariantGetInternalPtr<int64_t>::get_ptr(&p_index), p_value, valid, obb);
if (obb) {
valid = false;
+ if (err_code) {
+ *err_code = VariantSetError::SET_INDEXED_ERR;
+ }
}
} else if (p_index.get_type() == STRING) { // less efficient version of named
set_named(*VariantGetInternalPtr<String>::get_ptr(&p_index), p_value, valid);
+ if (!valid && err_code) {
+ *err_code = VariantSetError::SET_NAMED_ERR;
+ }
} else if (p_index.get_type() == FLOAT) { // less efficient version of indexed
bool obb;
set_indexed(*VariantGetInternalPtr<double>::get_ptr(&p_index), p_value, valid, obb);
if (obb) {
valid = false;
+ if (err_code) {
+ *err_code = VariantSetError::SET_INDEXED_ERR;
+ }
}
}
if (r_valid) {
@@ -1198,31 +1221,49 @@ void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid)
}
}
-Variant Variant::get(const Variant &p_index, bool *r_valid) const {
+Variant Variant::get(const Variant &p_index, bool *r_valid, VariantGetError *err_code) const {
+ if (err_code) {
+ *err_code = VariantGetError::GET_OK;
+ }
Variant ret;
if (type == DICTIONARY || type == OBJECT) {
bool valid;
ret = get_keyed(p_index, valid);
if (r_valid) {
*r_valid = valid;
+ if (!valid && err_code) {
+ *err_code = VariantGetError::GET_KEYED_ERR;
+ }
}
} else {
bool valid = false;
if (p_index.get_type() == STRING_NAME) {
ret = get_named(*VariantGetInternalPtr<StringName>::get_ptr(&p_index), valid);
+ if (!valid && err_code) {
+ *err_code = VariantGetError::GET_NAMED_ERR;
+ }
} else if (p_index.get_type() == INT) {
bool obb;
ret = get_indexed(*VariantGetInternalPtr<int64_t>::get_ptr(&p_index), valid, obb);
if (obb) {
valid = false;
+ if (err_code) {
+ *err_code = VariantGetError::GET_INDEXED_ERR;
+ }
}
} else if (p_index.get_type() == STRING) { // less efficient version of named
ret = get_named(*VariantGetInternalPtr<String>::get_ptr(&p_index), valid);
+ if (!valid && err_code) {
+ *err_code = VariantGetError::GET_NAMED_ERR;
+ }
} else if (p_index.get_type() == FLOAT) { // less efficient version of indexed
bool obb;
ret = get_indexed(*VariantGetInternalPtr<double>::get_ptr(&p_index), valid, obb);
if (obb) {
valid = false;
+ if (err_code) {
+ *err_code = VariantGetError::GET_INDEXED_ERR;
+ }
}
}
if (r_valid) {