summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/debugger/remote_debugger.cpp33
-rw-r--r--core/debugger/remote_debugger.h1
-rw-r--r--core/input/input.cpp4
-rw-r--r--core/input/input_builders.py4
-rw-r--r--core/io/image.h4
-rw-r--r--core/io/packet_peer.cpp6
-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/math/aabb.h6
-rw-r--r--core/math/geometry_2d.h26
-rw-r--r--core/object/class_db.cpp7
-rw-r--r--core/object/object.cpp2
-rw-r--r--core/object/script_language.h1
-rw-r--r--core/object/script_language_extension.h1
-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/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/templates/command_queue_mt.h50
-rw-r--r--core/templates/cowdata.h152
-rw-r--r--core/templates/paged_allocator.h6
-rw-r--r--core/templates/ring_buffer.h2
-rw-r--r--core/templates/vector.h69
-rw-r--r--core/variant/callable.cpp3
-rw-r--r--core/variant/variant_call.cpp2
34 files changed, 1831 insertions, 453 deletions
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/input/input.cpp b/core/input/input.cpp
index 2ba4b1d1e8..4e33d3087d 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -865,6 +865,8 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con
}
void Input::action_press(const StringName &p_action, float p_strength) {
+ ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action));
+
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
@@ -879,6 +881,8 @@ void Input::action_press(const StringName &p_action, float p_strength) {
}
void Input::action_release(const StringName &p_action) {
+ ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action));
+
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
action_state.cache.pressed = 0;
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/io/image.h b/core/io/image.h
index a5025ee8a1..be308b0ac1 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -313,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/packet_peer.cpp b/core/io/packet_peer.cpp
index 48806fba73..0329ace313 100644
--- a/core/io/packet_peer.cpp
+++ b/core/io/packet_peer.cpp
@@ -318,9 +318,9 @@ int PacketPeerStream::get_output_buffer_max_size() const {
}
PacketPeerStream::PacketPeerStream() {
- int rbsize = GLOBAL_GET("network/limits/packet_peer_stream/max_buffer_po2");
+ int64_t rbsize = GLOBAL_GET("network/limits/packet_peer_stream/max_buffer_po2");
ring_buffer.resize(rbsize);
- input_buffer.resize(1 << rbsize);
- output_buffer.resize(1 << rbsize);
+ input_buffer.resize(int64_t(1) << rbsize);
+ output_buffer.resize(int64_t(1) << rbsize);
}
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/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/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/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/script_language.h b/core/object/script_language.h
index 66106bf139..bb714d5bc3 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -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.h b/core/object/script_language_extension.h
index 8b01667519..5b10739486 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -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/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/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/cowdata.h b/core/templates/cowdata.h
index 46d9797d6c..d43cf8107f 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -46,7 +46,7 @@ class CharString;
template <class T, class V>
class VMap;
-SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t)
+SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint64_t)
// Silence a false positive warning (see GH-52119).
#if defined(__GNUC__) && !defined(__clang__)
@@ -64,45 +64,71 @@ class CowData {
template <class TV, class VV>
friend class VMap;
+public:
+ typedef int64_t Size;
+ typedef uint64_t USize;
+ static constexpr USize MAX_INT = INT64_MAX;
+
private:
+ // Function to find the next power of 2 to an integer.
+ static _FORCE_INLINE_ USize next_po2(USize x) {
+ if (x == 0) {
+ return 0;
+ }
+
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ if (sizeof(USize) == 8) {
+ x |= x >> 32;
+ }
+
+ return ++x;
+ }
+
+ static constexpr USize ALLOC_PAD = sizeof(USize) * 2; // For size and atomic refcount.
+
mutable T *_ptr = nullptr;
// internal helpers
- _FORCE_INLINE_ SafeNumeric<uint32_t> *_get_refcount() const {
+ _FORCE_INLINE_ SafeNumeric<USize> *_get_refcount() const {
if (!_ptr) {
return nullptr;
}
- return reinterpret_cast<SafeNumeric<uint32_t> *>(_ptr) - 2;
+ return reinterpret_cast<SafeNumeric<USize> *>(_ptr) - 2;
}
- _FORCE_INLINE_ uint32_t *_get_size() const {
+ _FORCE_INLINE_ USize *_get_size() const {
if (!_ptr) {
return nullptr;
}
- return reinterpret_cast<uint32_t *>(_ptr) - 1;
+ return reinterpret_cast<USize *>(_ptr) - 1;
}
- _FORCE_INLINE_ size_t _get_alloc_size(size_t p_elements) const {
- return next_power_of_2(p_elements * sizeof(T));
+ _FORCE_INLINE_ USize _get_alloc_size(USize p_elements) const {
+ return next_po2(p_elements * sizeof(T));
}
- _FORCE_INLINE_ bool _get_alloc_size_checked(size_t p_elements, size_t *out) const {
+ _FORCE_INLINE_ bool _get_alloc_size_checked(USize p_elements, USize *out) const {
if (unlikely(p_elements == 0)) {
*out = 0;
return true;
}
-#if defined(__GNUC__)
- size_t o;
- size_t p;
+#if defined(__GNUC__) && defined(IS_32_BIT)
+ USize o;
+ USize p;
if (__builtin_mul_overflow(p_elements, sizeof(T), &o)) {
*out = 0;
return false;
}
- *out = next_power_of_2(o);
- if (__builtin_add_overflow(o, static_cast<size_t>(32), &p)) {
+ *out = next_po2(o);
+ if (__builtin_add_overflow(o, static_cast<USize>(32), &p)) {
return false; // No longer allocated here.
}
#else
@@ -116,7 +142,7 @@ private:
void _unref(void *p_data);
void _ref(const CowData *p_from);
void _ref(const CowData &p_from);
- uint32_t _copy_on_write();
+ USize _copy_on_write();
public:
void operator=(const CowData<T> &p_from) { _ref(p_from); }
@@ -130,8 +156,8 @@ public:
return _ptr;
}
- _FORCE_INLINE_ int size() const {
- uint32_t *size = (uint32_t *)_get_size();
+ _FORCE_INLINE_ Size size() const {
+ USize *size = (USize *)_get_size();
if (size) {
return *size;
} else {
@@ -142,42 +168,42 @@ public:
_FORCE_INLINE_ void clear() { resize(0); }
_FORCE_INLINE_ bool is_empty() const { return _ptr == nullptr; }
- _FORCE_INLINE_ void set(int p_index, const T &p_elem) {
+ _FORCE_INLINE_ void set(Size p_index, const T &p_elem) {
ERR_FAIL_INDEX(p_index, size());
_copy_on_write();
_ptr[p_index] = p_elem;
}
- _FORCE_INLINE_ T &get_m(int p_index) {
+ _FORCE_INLINE_ T &get_m(Size p_index) {
CRASH_BAD_INDEX(p_index, size());
_copy_on_write();
return _ptr[p_index];
}
- _FORCE_INLINE_ const T &get(int p_index) const {
+ _FORCE_INLINE_ const T &get(Size p_index) const {
CRASH_BAD_INDEX(p_index, size());
return _ptr[p_index];
}
template <bool p_ensure_zero = false>
- Error resize(int p_size);
+ Error resize(Size p_size);
- _FORCE_INLINE_ void remove_at(int p_index) {
+ _FORCE_INLINE_ void remove_at(Size p_index) {
ERR_FAIL_INDEX(p_index, size());
T *p = ptrw();
- int len = size();
- for (int i = p_index; i < len - 1; i++) {
+ Size len = size();
+ for (Size i = p_index; i < len - 1; i++) {
p[i] = p[i + 1];
}
resize(len - 1);
}
- Error insert(int p_pos, const T &p_val) {
+ Error insert(Size p_pos, const T &p_val) {
ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER);
resize(size() + 1);
- for (int i = (size() - 1); i > p_pos; i--) {
+ for (Size i = (size() - 1); i > p_pos; i--) {
set(i, get(i - 1));
}
set(p_pos, p_val);
@@ -185,9 +211,9 @@ public:
return OK;
}
- int find(const T &p_val, int p_from = 0) const;
- int rfind(const T &p_val, int p_from = -1) const;
- int count(const T &p_val) const;
+ Size find(const T &p_val, Size p_from = 0) const;
+ Size rfind(const T &p_val, Size p_from = -1) const;
+ Size count(const T &p_val) const;
_FORCE_INLINE_ CowData() {}
_FORCE_INLINE_ ~CowData();
@@ -200,7 +226,7 @@ void CowData<T>::_unref(void *p_data) {
return;
}
- SafeNumeric<uint32_t> *refc = _get_refcount();
+ SafeNumeric<USize> *refc = _get_refcount();
if (refc->decrement() > 0) {
return; // still in use
@@ -208,35 +234,36 @@ void CowData<T>::_unref(void *p_data) {
// clean up
if (!std::is_trivially_destructible<T>::value) {
- uint32_t *count = _get_size();
+ USize *count = _get_size();
T *data = (T *)(count + 1);
- for (uint32_t i = 0; i < *count; ++i) {
+ for (USize i = 0; i < *count; ++i) {
// call destructors
data[i].~T();
}
}
// free mem
- Memory::free_static((uint8_t *)p_data, true);
+ Memory::free_static(((uint8_t *)p_data) - ALLOC_PAD, false);
}
template <class T>
-uint32_t CowData<T>::_copy_on_write() {
+typename CowData<T>::USize CowData<T>::_copy_on_write() {
if (!_ptr) {
return 0;
}
- SafeNumeric<uint32_t> *refc = _get_refcount();
+ SafeNumeric<USize> *refc = _get_refcount();
- uint32_t rc = refc->get();
+ USize rc = refc->get();
if (unlikely(rc > 1)) {
/* in use by more than me */
- uint32_t current_size = *_get_size();
+ USize current_size = *_get_size();
- uint32_t *mem_new = (uint32_t *)Memory::alloc_static(_get_alloc_size(current_size), true);
+ USize *mem_new = (USize *)Memory::alloc_static(_get_alloc_size(current_size) + ALLOC_PAD, false);
+ mem_new += 2;
- new (mem_new - 2) SafeNumeric<uint32_t>(1); //refcount
+ new (mem_new - 2) SafeNumeric<USize>(1); //refcount
*(mem_new - 1) = current_size; //size
T *_data = (T *)(mem_new);
@@ -246,7 +273,7 @@ uint32_t CowData<T>::_copy_on_write() {
memcpy(mem_new, _ptr, current_size * sizeof(T));
} else {
- for (uint32_t i = 0; i < current_size; i++) {
+ for (USize i = 0; i < current_size; i++) {
memnew_placement(&_data[i], T(_ptr[i]));
}
}
@@ -261,10 +288,10 @@ uint32_t CowData<T>::_copy_on_write() {
template <class T>
template <bool p_ensure_zero>
-Error CowData<T>::resize(int p_size) {
+Error CowData<T>::resize(Size p_size) {
ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
- int current_size = size();
+ Size current_size = size();
if (p_size == current_size) {
return OK;
@@ -278,27 +305,29 @@ Error CowData<T>::resize(int p_size) {
}
// possibly changing size, copy on write
- uint32_t rc = _copy_on_write();
+ USize rc = _copy_on_write();
- size_t current_alloc_size = _get_alloc_size(current_size);
- size_t alloc_size;
+ USize current_alloc_size = _get_alloc_size(current_size);
+ USize alloc_size;
ERR_FAIL_COND_V(!_get_alloc_size_checked(p_size, &alloc_size), ERR_OUT_OF_MEMORY);
if (p_size > current_size) {
if (alloc_size != current_alloc_size) {
if (current_size == 0) {
// alloc from scratch
- uint32_t *ptr = (uint32_t *)Memory::alloc_static(alloc_size, true);
+ USize *ptr = (USize *)Memory::alloc_static(alloc_size + ALLOC_PAD, false);
+ ptr += 2;
ERR_FAIL_NULL_V(ptr, ERR_OUT_OF_MEMORY);
*(ptr - 1) = 0; //size, currently none
- new (ptr - 2) SafeNumeric<uint32_t>(1); //refcount
+ new (ptr - 2) SafeNumeric<USize>(1); //refcount
_ptr = (T *)ptr;
} else {
- uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true);
+ USize *_ptrnew = (USize *)Memory::realloc_static(((uint8_t *)_ptr) - ALLOC_PAD, alloc_size + ALLOC_PAD, false);
ERR_FAIL_NULL_V(_ptrnew, ERR_OUT_OF_MEMORY);
- new (_ptrnew - 2) SafeNumeric<uint32_t>(rc); //refcount
+ _ptrnew += 2;
+ new (_ptrnew - 2) SafeNumeric<USize>(rc); //refcount
_ptr = (T *)(_ptrnew);
}
@@ -307,7 +336,7 @@ Error CowData<T>::resize(int p_size) {
// construct the newly created elements
if (!std::is_trivially_constructible<T>::value) {
- for (int i = *_get_size(); i < p_size; i++) {
+ for (Size i = *_get_size(); i < p_size; i++) {
memnew_placement(&_ptr[i], T);
}
} else if (p_ensure_zero) {
@@ -319,16 +348,17 @@ Error CowData<T>::resize(int p_size) {
} else if (p_size < current_size) {
if (!std::is_trivially_destructible<T>::value) {
// deinitialize no longer needed elements
- for (uint32_t i = p_size; i < *_get_size(); i++) {
+ for (USize i = p_size; i < *_get_size(); i++) {
T *t = &_ptr[i];
t->~T();
}
}
if (alloc_size != current_alloc_size) {
- uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true);
+ USize *_ptrnew = (USize *)Memory::realloc_static(((uint8_t *)_ptr) - ALLOC_PAD, alloc_size + ALLOC_PAD, false);
ERR_FAIL_NULL_V(_ptrnew, ERR_OUT_OF_MEMORY);
- new (_ptrnew - 2) SafeNumeric<uint32_t>(rc); //refcount
+ _ptrnew += 2;
+ new (_ptrnew - 2) SafeNumeric<USize>(rc); //refcount
_ptr = (T *)(_ptrnew);
}
@@ -340,14 +370,14 @@ Error CowData<T>::resize(int p_size) {
}
template <class T>
-int CowData<T>::find(const T &p_val, int p_from) const {
- int ret = -1;
+typename CowData<T>::Size CowData<T>::find(const T &p_val, Size p_from) const {
+ Size ret = -1;
if (p_from < 0 || size() == 0) {
return ret;
}
- for (int i = p_from; i < size(); i++) {
+ for (Size i = p_from; i < size(); i++) {
if (get(i) == p_val) {
ret = i;
break;
@@ -358,8 +388,8 @@ int CowData<T>::find(const T &p_val, int p_from) const {
}
template <class T>
-int CowData<T>::rfind(const T &p_val, int p_from) const {
- const int s = size();
+typename CowData<T>::Size CowData<T>::rfind(const T &p_val, Size p_from) const {
+ const Size s = size();
if (p_from < 0) {
p_from = s + p_from;
@@ -368,7 +398,7 @@ int CowData<T>::rfind(const T &p_val, int p_from) const {
p_from = s - 1;
}
- for (int i = p_from; i >= 0; i--) {
+ for (Size i = p_from; i >= 0; i--) {
if (get(i) == p_val) {
return i;
}
@@ -377,9 +407,9 @@ int CowData<T>::rfind(const T &p_val, int p_from) const {
}
template <class T>
-int CowData<T>::count(const T &p_val) const {
- int amount = 0;
- for (int i = 0; i < size(); i++) {
+typename CowData<T>::Size CowData<T>::count(const T &p_val) const {
+ Size amount = 0;
+ for (Size i = 0; i < size(); i++) {
if (get(i) == p_val) {
amount++;
}
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/ring_buffer.h b/core/templates/ring_buffer.h
index d878894946..54148a59bf 100644
--- a/core/templates/ring_buffer.h
+++ b/core/templates/ring_buffer.h
@@ -197,7 +197,7 @@ public:
int old_size = size();
int new_size = 1 << p_power;
int mask = new_size - 1;
- data.resize(1 << p_power);
+ data.resize(int64_t(1) << int64_t(p_power));
if (old_size < new_size && read_pos > write_pos) {
for (int i = 0; i < write_pos; i++) {
data.write[(old_size + i) & mask] = data[i];
diff --git a/core/templates/vector.h b/core/templates/vector.h
index d8bac0870f..361a7f56d5 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -48,7 +48,7 @@
template <class T>
class VectorWriteProxy {
public:
- _FORCE_INLINE_ T &operator[](int p_index) {
+ _FORCE_INLINE_ T &operator[](typename CowData<T>::Size p_index) {
CRASH_BAD_INDEX(p_index, ((Vector<T> *)(this))->_cowdata.size());
return ((Vector<T> *)(this))->_cowdata.ptrw()[p_index];
@@ -61,6 +61,7 @@ class Vector {
public:
VectorWriteProxy<T> write;
+ typedef typename CowData<T>::Size Size;
private:
CowData<T> _cowdata;
@@ -70,9 +71,9 @@ public:
_FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias
void fill(T p_elem);
- void remove_at(int p_index) { _cowdata.remove_at(p_index); }
+ void remove_at(Size p_index) { _cowdata.remove_at(p_index); }
_FORCE_INLINE_ bool erase(const T &p_val) {
- int idx = find(p_val);
+ Size idx = find(p_val);
if (idx >= 0) {
remove_at(idx);
return true;
@@ -87,17 +88,17 @@ public:
_FORCE_INLINE_ void clear() { resize(0); }
_FORCE_INLINE_ bool is_empty() const { return _cowdata.is_empty(); }
- _FORCE_INLINE_ T get(int p_index) { return _cowdata.get(p_index); }
- _FORCE_INLINE_ const T &get(int p_index) const { return _cowdata.get(p_index); }
- _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
- _FORCE_INLINE_ int size() const { return _cowdata.size(); }
- Error resize(int p_size) { return _cowdata.resize(p_size); }
- Error resize_zeroed(int p_size) { return _cowdata.template resize<true>(p_size); }
- _FORCE_INLINE_ const T &operator[](int p_index) const { return _cowdata.get(p_index); }
- Error insert(int p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }
- int find(const T &p_val, int p_from = 0) const { return _cowdata.find(p_val, p_from); }
- int rfind(const T &p_val, int p_from = -1) const { return _cowdata.rfind(p_val, p_from); }
- int count(const T &p_val) const { return _cowdata.count(p_val); }
+ _FORCE_INLINE_ T get(Size p_index) { return _cowdata.get(p_index); }
+ _FORCE_INLINE_ const T &get(Size p_index) const { return _cowdata.get(p_index); }
+ _FORCE_INLINE_ void set(Size p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
+ _FORCE_INLINE_ Size size() const { return _cowdata.size(); }
+ Error resize(Size p_size) { return _cowdata.resize(p_size); }
+ Error resize_zeroed(Size p_size) { return _cowdata.template resize<true>(p_size); }
+ _FORCE_INLINE_ const T &operator[](Size p_index) const { return _cowdata.get(p_index); }
+ Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }
+ Size find(const T &p_val, Size p_from = 0) const { return _cowdata.find(p_val, p_from); }
+ Size rfind(const T &p_val, Size p_from = -1) const { return _cowdata.rfind(p_val, p_from); }
+ Size count(const T &p_val) const { return _cowdata.count(p_val); }
void append_array(Vector<T> p_other);
@@ -109,7 +110,7 @@ public:
template <class Comparator, bool Validate = SORT_ARRAY_VALIDATE_ENABLED, class... Args>
void sort_custom(Args &&...args) {
- int len = _cowdata.size();
+ Size len = _cowdata.size();
if (len == 0) {
return;
}
@@ -119,12 +120,12 @@ public:
sorter.sort(data, len);
}
- int bsearch(const T &p_value, bool p_before) {
+ Size bsearch(const T &p_value, bool p_before) {
return bsearch_custom<_DefaultComparator<T>>(p_value, p_before);
}
template <class Comparator, class Value, class... Args>
- int bsearch_custom(const Value &p_value, bool p_before, Args &&...args) {
+ Size bsearch_custom(const Value &p_value, bool p_before, Args &&...args) {
SearchArray<T, Comparator> search{ args... };
return search.bisect(ptrw(), size(), p_value, p_before);
}
@@ -134,7 +135,7 @@ public:
}
void ordered_insert(const T &p_val) {
- int i;
+ Size i;
for (i = 0; i < _cowdata.size(); i++) {
if (p_val < operator[](i)) {
break;
@@ -157,28 +158,28 @@ public:
return ret;
}
- Vector<T> slice(int p_begin, int p_end = INT_MAX) const {
+ Vector<T> slice(Size p_begin, Size p_end = CowData<T>::MAX_INT) const {
Vector<T> result;
- const int s = size();
+ const Size s = size();
- int begin = CLAMP(p_begin, -s, s);
+ Size begin = CLAMP(p_begin, -s, s);
if (begin < 0) {
begin += s;
}
- int end = CLAMP(p_end, -s, s);
+ Size end = CLAMP(p_end, -s, s);
if (end < 0) {
end += s;
}
ERR_FAIL_COND_V(begin > end, result);
- int result_size = end - begin;
+ Size result_size = end - begin;
result.resize(result_size);
const T *const r = ptr();
T *const w = result.ptrw();
- for (int i = 0; i < result_size; ++i) {
+ for (Size i = 0; i < result_size; ++i) {
w[i] = r[begin + i];
}
@@ -186,11 +187,11 @@ public:
}
bool operator==(const Vector<T> &p_arr) const {
- int s = size();
+ Size s = size();
if (s != p_arr.size()) {
return false;
}
- for (int i = 0; i < s; i++) {
+ for (Size i = 0; i < s; i++) {
if (operator[](i) != p_arr[i]) {
return false;
}
@@ -199,11 +200,11 @@ public:
}
bool operator!=(const Vector<T> &p_arr) const {
- int s = size();
+ Size s = size();
if (s != p_arr.size()) {
return true;
}
- for (int i = 0; i < s; i++) {
+ for (Size i = 0; i < s; i++) {
if (operator[](i) != p_arr[i]) {
return true;
}
@@ -280,7 +281,7 @@ public:
Error err = _cowdata.resize(p_init.size());
ERR_FAIL_COND(err);
- int i = 0;
+ Size i = 0;
for (const T &element : p_init) {
_cowdata.set(i++, element);
}
@@ -292,7 +293,7 @@ public:
template <class T>
void Vector<T>::reverse() {
- for (int i = 0; i < size() / 2; i++) {
+ for (Size i = 0; i < size() / 2; i++) {
T *p = ptrw();
SWAP(p[i], p[size() - i - 1]);
}
@@ -300,13 +301,13 @@ void Vector<T>::reverse() {
template <class T>
void Vector<T>::append_array(Vector<T> p_other) {
- const int ds = p_other.size();
+ const Size ds = p_other.size();
if (ds == 0) {
return;
}
- const int bs = size();
+ const Size bs = size();
resize(bs + ds);
- for (int i = 0; i < ds; ++i) {
+ for (Size i = 0; i < ds; ++i) {
ptrw()[bs + i] = p_other[i];
}
}
@@ -323,7 +324,7 @@ bool Vector<T>::push_back(T p_elem) {
template <class T>
void Vector<T>::fill(T p_elem) {
T *p = ptrw();
- for (int i = 0; i < size(); i++) {
+ for (Size i = 0; i < size(); i++) {
p[i] = p_elem;
}
}
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_call.cpp b/core/variant/variant_call.cpp
index 03836985f3..90f7e7b44a 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -666,7 +666,7 @@ struct _VariantCall {
CharString cs;
cs.resize(p_instance->size() + 1);
memcpy(cs.ptrw(), r, p_instance->size());
- cs[p_instance->size()] = 0;
+ cs[(int)p_instance->size()] = 0;
s = cs.get_data();
}