summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/engine.cpp20
-rw-r--r--core/config/engine.h11
-rw-r--r--core/config/project_settings.cpp52
-rw-r--r--core/core_bind.cpp54
-rw-r--r--core/core_bind.h6
-rw-r--r--core/crypto/crypto.cpp2
-rw-r--r--core/crypto/crypto_core.cpp4
-rw-r--r--core/debugger/debugger_marshalls.cpp34
-rw-r--r--core/debugger/debugger_marshalls.h4
-rw-r--r--core/debugger/engine_debugger.cpp18
-rw-r--r--core/debugger/engine_debugger.h2
-rw-r--r--core/debugger/remote_debugger.cpp4
-rw-r--r--core/debugger/remote_debugger_peer.cpp2
-rw-r--r--core/error/error_macros.cpp22
-rw-r--r--core/error/error_macros.h1
-rw-r--r--core/extension/extension_api_dump.cpp10
-rw-r--r--core/extension/gdextension.cpp30
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_library_loader.cpp10
-rw-r--r--core/extension/gdextension_manager.cpp2
-rw-r--r--core/input/input.cpp112
-rw-r--r--core/input/input.h10
-rw-r--r--core/input/input_map.cpp2
-rw-r--r--core/io/dir_access.cpp16
-rw-r--r--core/io/dir_access.h8
-rw-r--r--core/io/file_access.cpp20
-rw-r--r--core/io/file_access.h4
-rw-r--r--core/io/file_access_compressed.cpp2
-rw-r--r--core/io/file_access_encrypted.cpp2
-rw-r--r--core/io/file_access_memory.cpp2
-rw-r--r--core/io/file_access_pack.cpp78
-rw-r--r--core/io/file_access_pack.h13
-rw-r--r--core/io/file_access_zip.cpp4
-rw-r--r--core/io/http_client.cpp4
-rw-r--r--core/io/http_client_tcp.cpp7
-rw-r--r--core/io/image.cpp272
-rw-r--r--core/io/image.h251
-rw-r--r--core/io/image_loader.cpp4
-rw-r--r--core/io/ip.cpp6
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/io/marshalls.cpp405
-rw-r--r--core/io/missing_resource.cpp4
-rw-r--r--core/io/missing_resource.h2
-rw-r--r--core/io/net_socket.h2
-rw-r--r--core/io/packet_peer_udp.cpp18
-rw-r--r--core/io/pck_packer.cpp43
-rw-r--r--core/io/pck_packer.h4
-rw-r--r--core/io/plist.cpp2
-rw-r--r--core/io/remote_filesystem_client.cpp14
-rw-r--r--core/io/resource.cpp2
-rw-r--r--core/io/resource_format_binary.cpp70
-rw-r--r--core/io/resource_format_binary.h1
-rw-r--r--core/io/resource_importer.cpp8
-rw-r--r--core/io/resource_importer.h5
-rw-r--r--core/io/resource_loader.cpp111
-rw-r--r--core/io/resource_loader.h6
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/io/stream_peer.cpp4
-rw-r--r--core/io/tcp_server.cpp2
-rw-r--r--core/io/translation_loader_po.cpp33
-rw-r--r--core/io/xml_parser.cpp4
-rw-r--r--core/math/basis.h2
-rw-r--r--core/math/expression.cpp2
-rw-r--r--core/math/geometry_2d.cpp2
-rw-r--r--core/math/geometry_2d.h12
-rw-r--r--core/math/plane.h2
-rw-r--r--core/math/rect2.cpp2
-rw-r--r--core/math/vector2.cpp2
-rw-r--r--core/math/vector3.cpp2
-rw-r--r--core/object/class_db.cpp186
-rw-r--r--core/object/message_queue.h2
-rw-r--r--core/object/method_bind.h2
-rw-r--r--core/object/object.cpp40
-rw-r--r--core/object/object.h24
-rw-r--r--core/object/script_language.cpp6
-rw-r--r--core/object/script_language.h4
-rw-r--r--core/object/undo_redo.cpp2
-rw-r--r--core/os/os.cpp12
-rw-r--r--core/os/os.h8
-rw-r--r--core/os/spin_lock.h47
-rw-r--r--core/os/thread.h18
-rw-r--r--core/string/char_range.inc613
-rw-r--r--core/string/char_utils.h66
-rw-r--r--core/string/fuzzy_search.cpp349
-rw-r--r--core/string/fuzzy_search.h101
-rw-r--r--core/string/node_path.cpp2
-rw-r--r--core/string/translation_domain.cpp7
-rw-r--r--core/string/translation_po.cpp10
-rw-r--r--core/string/translation_server.cpp137
-rw-r--r--core/string/translation_server.h18
-rw-r--r--core/string/ustring.cpp60
-rw-r--r--core/string/ustring.h9
-rw-r--r--core/templates/a_hash_map.cpp39
-rw-r--r--core/templates/a_hash_map.h732
-rw-r--r--core/templates/cowdata.h2
-rw-r--r--core/templates/hashfuncs.h17
-rw-r--r--core/templates/list.h6
-rw-r--r--core/templates/lru.h52
-rw-r--r--core/templates/rb_set.h2
-rw-r--r--core/variant/array.cpp14
-rw-r--r--core/variant/callable_bind.cpp4
-rw-r--r--core/variant/container_type_validate.h10
-rw-r--r--core/variant/variant.cpp16
-rw-r--r--core/variant/variant.h3
-rw-r--r--core/variant/variant_construct.cpp2
-rw-r--r--core/variant/variant_op.cpp2
-rw-r--r--core/variant/variant_setget.cpp4
-rw-r--r--core/variant/variant_utility.cpp2
108 files changed, 3362 insertions, 1145 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index d77c913314..aac048e93f 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -36,6 +36,7 @@
#include "core/license.gen.h"
#include "core/variant/typed_array.h"
#include "core/version.h"
+#include "servers/rendering/rendering_device.h"
void Engine::set_physics_ticks_per_second(int p_ips) {
ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0.");
@@ -68,6 +69,11 @@ double Engine::get_physics_jitter_fix() const {
void Engine::set_max_fps(int p_fps) {
_max_fps = p_fps > 0 ? p_fps : 0;
+
+ RenderingDevice *rd = RenderingDevice::get_singleton();
+ if (rd) {
+ rd->_set_max_fps(_max_fps);
+ }
}
int Engine::get_max_fps() const {
@@ -110,6 +116,10 @@ void Engine::set_time_scale(double p_scale) {
}
double Engine::get_time_scale() const {
+ return freeze_time_scale ? 0 : _time_scale;
+}
+
+double Engine::get_unfrozen_time_scale() const {
return _time_scale;
}
@@ -267,6 +277,12 @@ bool Engine::is_extra_gpu_memory_tracking_enabled() const {
return extra_gpu_memory_tracking;
}
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+bool Engine::is_accurate_breadcrumbs_enabled() const {
+ return accurate_breadcrumbs;
+}
+#endif
+
void Engine::set_print_to_stdout(bool p_enabled) {
CoreGlobals::print_line_enabled = p_enabled;
}
@@ -392,6 +408,10 @@ bool Engine::notify_frame_server_synced() {
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
}
+void Engine::set_freeze_time_scale(bool p_frozen) {
+ freeze_time_scale = p_frozen;
+}
+
Engine::Engine() {
singleton = this;
}
diff --git a/core/config/engine.h b/core/config/engine.h
index a0b1ffa981..b38412308a 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -73,6 +73,9 @@ private:
bool use_validation_layers = false;
bool generate_spirv_debug_info = false;
bool extra_gpu_memory_tracking = false;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ bool accurate_breadcrumbs = false;
+#endif
int32_t gpu_idx = -1;
uint64_t _process_frames = 0;
@@ -96,6 +99,8 @@ private:
int server_syncs = 0;
bool frame_server_synced = false;
+ bool freeze_time_scale = false;
+
public:
static Engine *get_singleton();
@@ -127,6 +132,7 @@ public:
void set_time_scale(double p_scale);
double get_time_scale() const;
+ double get_unfrozen_time_scale() const;
void set_print_to_stdout(bool p_enabled);
bool is_printing_to_stdout() const;
@@ -186,11 +192,16 @@ public:
bool is_validation_layers_enabled() const;
bool is_generate_spirv_debug_info_enabled() const;
bool is_extra_gpu_memory_tracking_enabled() const;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ bool is_accurate_breadcrumbs_enabled() const;
+#endif
int32_t get_gpu_index() const;
void increment_frames_drawn();
bool notify_frame_server_synced();
+ void set_freeze_time_scale(bool p_frozen);
+
Engine();
virtual ~Engine();
};
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 52677e80c3..092177bc15 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -214,36 +214,36 @@ String ProjectSettings::localize_path(const String &p_path) const {
}
void ProjectSettings::set_initial_value(const String &p_name, const Variant &p_value) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
// Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
props[p_name].initial = p_value.duplicate();
}
void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restart) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].restart_if_changed = p_restart;
}
void ProjectSettings::set_as_basic(const String &p_name, bool p_basic) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].basic = p_basic;
}
void ProjectSettings::set_as_internal(const String &p_name, bool p_internal) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].internal = p_internal;
}
void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
props[p_name].ignore_value_in_docs = p_ignore;
#endif
}
bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), false, vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
return props[p_name].ignore_value_in_docs;
#else
@@ -371,7 +371,7 @@ Variant ProjectSettings::get_setting_with_override(const StringName &p_name) con
}
if (!props.has(name)) {
- WARN_PRINT("Property not found: " + String(name));
+ WARN_PRINT(vformat("Property not found: '%s'.", String(name)));
return Variant();
}
return props[name].variant;
@@ -515,9 +515,9 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
Dictionary action = E.value.variant;
Array events = action["events"];
for (int i = 0; i < events.size(); i++) {
- Ref<InputEvent> x = events[i];
- if (x->get_device() == -1) { // -1 was the previous value (GH-97707).
- x->set_device(InputEvent::DEVICE_ID_ALL_DEVICES);
+ Ref<InputEvent> ev = events[i];
+ if (ev.is_valid() && ev->get_device() == -1) { // -1 was the previous value (GH-97707).
+ ev->set_device(InputEvent::DEVICE_ID_ALL_DEVICES);
}
}
}
@@ -565,7 +565,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
if (!p_main_pack.is_empty()) {
bool ok = _load_resource_pack(p_main_pack);
- ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, "Cannot open resource pack '" + p_main_pack + "'.");
+ ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack));
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK && !p_ignore_override) {
@@ -644,7 +644,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
// or, if requested (`p_upwards`) in parent directories.
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_path));
d->change_dir(p_path);
String current_dir = d->get_current_dir();
@@ -748,7 +748,7 @@ Error ProjectSettings::_load_settings_binary(const String &p_path) {
f->get_buffer(d.ptrw(), vlen);
Variant value;
err = decode_variant(value, d.ptr(), d.size(), nullptr, true);
- ERR_CONTINUE_MSG(err != OK, "Error decoding property: " + key + ".");
+ ERR_CONTINUE_MSG(err != OK, vformat("Error decoding property: '%s'.", key));
set(key, value);
}
@@ -790,7 +790,7 @@ Error ProjectSettings::_load_settings_text(const String &p_path) {
last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));
return OK;
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing " + p_path + " at line " + itos(lines) + ": " + error_text + " File might be corrupted.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing '%s' at line %d: %s File might be corrupted.", p_path, lines, error_text));
if (!assign.is_empty()) {
if (section.is_empty() && assign == "config_version") {
@@ -816,7 +816,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
// If the file exists but can't be loaded, we want to know it.
- ERR_PRINT("Couldn't load file '" + p_bin_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_bin_path, err));
}
// Fallback to text-based project.godot file if binary was not found.
@@ -824,7 +824,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
if (err == OK) {
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
- ERR_PRINT("Couldn't load file '" + p_text_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_text_path, err));
}
return err;
@@ -838,17 +838,17 @@ Error ProjectSettings::load_custom(const String &p_path) {
}
int ProjectSettings::get_order(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order;
}
void ProjectSettings::set_order(const String &p_name, int p_order) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].order = p_order;
}
void ProjectSettings::set_builtin_order(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
if (props[p_name].order >= NO_BUILTIN_ORDER_BASE) {
props[p_name].order = last_builtin_order++;
}
@@ -856,12 +856,12 @@ void ProjectSettings::set_builtin_order(const String &p_name) {
bool ProjectSettings::is_builtin_setting(const String &p_name) const {
// Return true because a false negative is worse than a false positive.
- ERR_FAIL_COND_V_MSG(!props.has(p_name), true, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), true, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order < NO_BUILTIN_ORDER_BASE;
}
void ProjectSettings::clear(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props.erase(p_name);
}
@@ -876,7 +876,7 @@ Error ProjectSettings::save() {
Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<String, List<String>> &p_props, const CustomMap &p_custom, const String &p_custom_features) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.binary at " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.binary at '%s'.", p_file));
uint8_t hdr[4] = { 'E', 'C', 'F', 'G' };
file->store_buffer(hdr, 4);
@@ -946,7 +946,7 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.godot - " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.godot - %s.", p_file));
file->store_line("; Engine configuration file.");
file->store_line("; It's best edited using the editor UI and not directly,");
@@ -1119,7 +1119,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
} else if (p_path.ends_with(".binary")) {
return _save_settings_binary(p_path, save_props, p_custom, save_features);
} else {
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown config file format: " + p_path);
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unknown config file format: '%s'.", p_path));
}
}
@@ -1503,6 +1503,10 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/subwindows/embed_subwindows", true);
// Keep the enum values in sync with the `DisplayServer::VSyncMode` enum.
custom_prop_info["display/window/vsync/vsync_mode"] = PropertyInfo(Variant::INT, "display/window/vsync/vsync_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Adaptive,Mailbox");
+
+ GLOBAL_DEF("display/window/frame_pacing/android/enable_frame_pacing", true);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/frame_pacing/android/swappy_mode", PROPERTY_HINT_ENUM, "pipeline_forced_on,auto_fps_pipeline_forced_on,auto_fps_auto_pipeline"), 2);
+
custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded");
GLOBAL_DEF("physics/2d/run_on_separate_thread", false);
GLOBAL_DEF("physics/3d/run_on_separate_thread", false);
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 891e3a28c9..967a7fba75 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -75,7 +75,7 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
Error err = OK;
Ref<Resource> ret = ::ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err);
- ERR_FAIL_COND_V_MSG(err != OK, ret, "Error loading resource: '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ret, vformat("Error loading resource: '%s'.", p_path));
return ret;
}
@@ -132,6 +132,10 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
return ::ResourceLoader::get_resource_uid(p_path);
}
+Vector<String> ResourceLoader::list_directory(const String &p_directory) {
+ return ::ResourceLoader::list_directory(p_directory);
+}
+
void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE));
ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY);
@@ -147,6 +151,7 @@ void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cached_ref", "path"), &ResourceLoader::get_cached_ref);
ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid);
+ ClassDB::bind_method(D_METHOD("list_directory", "directory_path"), &ResourceLoader::list_directory);
BIND_ENUM_CONSTANT(THREAD_LOAD_INVALID_RESOURCE);
BIND_ENUM_CONSTANT(THREAD_LOAD_IN_PROGRESS);
@@ -468,11 +473,11 @@ Error OS::set_thread_name(const String &p_name) {
::Thread::ID OS::get_thread_caller_id() const {
return ::Thread::get_caller_id();
-};
+}
::Thread::ID OS::get_main_thread_id() const {
return ::Thread::get_main_id();
-};
+}
bool OS::has_feature(const String &p_feature) const {
const bool *value_ptr = feature_cache.getptr(p_feature);
@@ -920,6 +925,19 @@ Dictionary Geometry2D::make_atlas(const Vector<Size2> &p_rects) {
return ret;
}
+TypedArray<Point2i> Geometry2D::bresenham_line(const Point2i &p_from, const Point2i &p_to) {
+ Vector<Point2i> points = ::Geometry2D::bresenham_line(p_from, p_to);
+
+ TypedArray<Point2i> result;
+ result.resize(points.size());
+
+ for (int i = 0; i < points.size(); i++) {
+ result[i] = points[i];
+ }
+
+ return result;
+}
+
void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_point_in_circle", "point", "circle_position", "circle_radius"), &Geometry2D::is_point_in_circle);
ClassDB::bind_method(D_METHOD("segment_intersects_circle", "segment_from", "segment_to", "circle_position", "circle_radius"), &Geometry2D::segment_intersects_circle);
@@ -954,6 +972,8 @@ void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &Geometry2D::make_atlas);
+ ClassDB::bind_method(D_METHOD("bresenham_line", "from", "to"), &Geometry2D::bresenham_line);
+
BIND_ENUM_CONSTANT(OPERATION_UNION);
BIND_ENUM_CONSTANT(OPERATION_DIFFERENCE);
BIND_ENUM_CONSTANT(OPERATION_INTERSECTION);
@@ -1300,7 +1320,7 @@ void Thread::_start_func(void *ud) {
}
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
+ ERR_FAIL_MSG(vformat("Could not call function '%s' to start thread %d: %s.", func_name, t->get_id(), Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce)));
}
}
@@ -1525,7 +1545,7 @@ TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class,
return ret;
}
-Variant ClassDB::class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
+Variant ClassDB::class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
if (p_argcount < 2) {
r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
return Variant::NIL;
@@ -1666,7 +1686,7 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
- ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static_method", &ClassDB::class_call_static_method, MethodInfo("class_call_static_method", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
+ ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static", &ClassDB::class_call_static, MethodInfo("class_call_static", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));
@@ -1799,8 +1819,8 @@ Object *Engine::get_singleton_object(const StringName &p_name) const {
}
void Engine::register_singleton(const StringName &p_name, Object *p_object) {
- ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name));
- ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name);
+ ERR_FAIL_COND_MSG(has_singleton(p_name), vformat("Singleton already registered: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), vformat("Singleton name is not a valid identifier: '%s'.", p_name));
::Engine::Singleton s;
s.class_name = p_name;
s.name = p_name;
@@ -1810,8 +1830,8 @@ void Engine::register_singleton(const StringName &p_name, Object *p_object) {
}
void Engine::unregister_singleton(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregistered singleton: " + String(p_name));
- ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name));
+ ERR_FAIL_COND_MSG(!has_singleton(p_name), vformat("Attempt to remove unregistered singleton: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), vformat("Attempt to remove non-user created singleton: '%s'.", String(p_name)));
::Engine::get_singleton()->remove_singleton(p_name);
}
@@ -1955,14 +1975,14 @@ bool EngineDebugger::is_active() {
void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
ERR_FAIL_COND(p_profiler.is_null());
ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
- ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), vformat("Profiler name already in use: '%s'.", p_name));
Error err = p_profiler->bind(p_name);
- ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
+ ERR_FAIL_COND_MSG(err != OK, vformat("Profiler failed to register with error: %d.", err));
profilers.insert(p_name, p_profiler);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
profilers[p_name]->unbind();
profilers.erase(p_name);
}
@@ -1986,7 +2006,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) {
- ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_callable);
Callable &c = captures[p_name];
::EngineDebugger::Capture capture(&c, &EngineDebugger::call_capture);
@@ -1994,7 +2014,7 @@ void EngineDebugger::register_message_capture(const StringName &p_name, const Ca
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
::EngineDebugger::unregister_message_capture(p_name);
captures.erase(p_name);
}
@@ -2028,8 +2048,8 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra
Variant retval;
Callable::CallError err;
capture.callp(args, 2, retval, err);
- ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(capture, args, 2, err));
- ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, "Error calling 'capture' to callable: " + String(capture) + ". Return type is not bool.");
+ ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, vformat("Error calling 'capture' to callable: %s.", Variant::get_callable_error_text(capture, args, 2, err)));
+ ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, vformat("Error calling 'capture' to callable: '%s'. Return type is not bool.", String(capture)));
r_captured = retval;
return OK;
}
diff --git a/core/core_bind.h b/core/core_bind.h
index d59a2c55f1..3ae54017fe 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -86,6 +86,8 @@ public:
bool exists(const String &p_path, const String &p_type_hint = "");
ResourceUID::ID get_resource_uid(const String &p_path);
+ Vector<String> list_directory(const String &p_directory);
+
ResourceLoader() { singleton = this; }
};
@@ -324,6 +326,8 @@ public:
Dictionary make_atlas(const Vector<Size2> &p_rects);
+ TypedArray<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to);
+
Geometry2D() { singleton = this; }
};
@@ -482,7 +486,7 @@ public:
int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
- Variant class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
+ Variant class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index 62bacadf91..30003e4ea8 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -240,7 +240,7 @@ Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const Str
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Crypto resource to file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save Crypto resource to file '%s'.", p_path));
return OK;
}
diff --git a/core/crypto/crypto_core.cpp b/core/crypto/crypto_core.cpp
index 69a83284cc..13852d5177 100644
--- a/core/crypto/crypto_core.cpp
+++ b/core/crypto/crypto_core.cpp
@@ -70,7 +70,7 @@ int CryptoCore::RandomGenerator::_entropy_poll(void *p_data, unsigned char *r_bu
Error CryptoCore::RandomGenerator::init() {
int ret = mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context *)ctx, mbedtls_entropy_func, (mbedtls_entropy_context *)entropy, nullptr, 0);
if (ret) {
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
}
return OK;
}
@@ -78,7 +78,7 @@ Error CryptoCore::RandomGenerator::init() {
Error CryptoCore::RandomGenerator::get_random_bytes(uint8_t *r_buffer, size_t p_bytes) {
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
int ret = mbedtls_ctr_drbg_random((mbedtls_ctr_drbg_context *)ctx, r_buffer, p_bytes);
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
return OK;
}
diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp
index f4283e0ea9..cc36ca4816 100644
--- a/core/debugger/debugger_marshalls.cpp
+++ b/core/debugger/debugger_marshalls.cpp
@@ -147,3 +147,37 @@ bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
CHECK_END(p_arr, idx, "OutputError");
return true;
}
+
+Array DebuggerMarshalls::serialize_key_shortcut(const Ref<Shortcut> &p_shortcut) {
+ ERR_FAIL_COND_V(p_shortcut.is_null(), Array());
+ Array keys;
+ for (const Ref<InputEvent> ev : p_shortcut->get_events()) {
+ const Ref<InputEventKey> kev = ev;
+ ERR_CONTINUE(kev.is_null());
+ if (kev->get_physical_keycode() != Key::NONE) {
+ keys.push_back(true);
+ keys.push_back(kev->get_physical_keycode_with_modifiers());
+ } else {
+ keys.push_back(false);
+ keys.push_back(kev->get_keycode_with_modifiers());
+ }
+ }
+ return keys;
+}
+
+Ref<Shortcut> DebuggerMarshalls::deserialize_key_shortcut(const Array &p_keys) {
+ Array key_events;
+ ERR_FAIL_COND_V(p_keys.size() % 2 != 0, Ref<Shortcut>());
+ for (int i = 0; i < p_keys.size(); i += 2) {
+ ERR_CONTINUE(p_keys[i].get_type() != Variant::BOOL);
+ ERR_CONTINUE(p_keys[i + 1].get_type() != Variant::INT);
+ key_events.push_back(InputEventKey::create_reference((Key)p_keys[i + 1].operator int(), p_keys[i].operator bool()));
+ }
+ if (key_events.is_empty()) {
+ return Ref<Shortcut>();
+ }
+ Ref<Shortcut> shortcut;
+ shortcut.instantiate();
+ shortcut->set_events(key_events);
+ return shortcut;
+}
diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h
index 1b81623688..1072ddaeb7 100644
--- a/core/debugger/debugger_marshalls.h
+++ b/core/debugger/debugger_marshalls.h
@@ -31,6 +31,7 @@
#ifndef DEBUGGER_MARSHALLS_H
#define DEBUGGER_MARSHALLS_H
+#include "core/input/shortcut.h"
#include "core/object/script_language.h"
struct DebuggerMarshalls {
@@ -68,6 +69,9 @@ struct DebuggerMarshalls {
Array serialize();
bool deserialize(const Array &p_arr);
};
+
+ static Array serialize_key_shortcut(const Ref<Shortcut> &p_shortcut);
+ static Ref<Shortcut> deserialize_key_shortcut(const Array &p_keys);
};
#endif // DEBUGGER_MARSHALLS_H
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
index 97a020e4c3..6d2868cef2 100644
--- a/core/debugger/engine_debugger.cpp
+++ b/core/debugger/engine_debugger.cpp
@@ -46,12 +46,12 @@ HashMap<String, EngineDebugger::CreatePeerFunc> EngineDebugger::protocols;
void (*EngineDebugger::allow_focus_steal_fn)();
void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
- ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name), vformat("Profiler already registered: '%s'.", p_name));
profilers.insert(p_name, p_func);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.active && p.toggle) {
p.toggle(p.data, false, Array());
@@ -61,22 +61,22 @@ void EngineDebugger::unregister_profiler(const StringName &p_name) {
}
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
- ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_func);
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
captures.erase(p_name);
}
void EngineDebugger::register_uri_handler(const String &p_protocol, CreatePeerFunc p_func) {
- ERR_FAIL_COND_MSG(protocols.has(p_protocol), "Protocol handler already registered: " + p_protocol);
+ ERR_FAIL_COND_MSG(protocols.has(p_protocol), vformat("Protocol handler already registered: '%s'.", p_protocol));
protocols.insert(p_protocol, p_func);
}
void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't change profiler state, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.toggle) {
p.toggle(p.data, p_enabled, p_opts);
@@ -85,7 +85,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't add frame data, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.add) {
p.add(p.data, p_data);
@@ -106,7 +106,7 @@ bool EngineDebugger::has_capture(const StringName &p_name) {
Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = false;
- ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
+ ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, vformat("Capture not registered: '%s'.", p_name));
const Capture &cap = captures[p_name];
return cap.capture(cap.data, p_msg, p_args, r_captured);
}
@@ -164,7 +164,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
for (int i = 0; i < p_breakpoints.size(); i++) {
const String &bp = p_breakpoints[i];
int sp = bp.rfind(":");
- ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
+ ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp));
singleton_script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}
diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h
index 16050778aa..3c4ac87408 100644
--- a/core/debugger/engine_debugger.h
+++ b/core/debugger/engine_debugger.h
@@ -106,7 +106,7 @@ public:
_FORCE_INLINE_ static EngineDebugger *get_singleton() { return singleton; }
_FORCE_INLINE_ static bool is_active() { return singleton != nullptr && script_debugger != nullptr; }
- _FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; };
+ _FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; }
static void initialize(const String &p_uri, bool p_skip_breakpoints, const Vector<String> &p_breakpoints, void (*p_allow_focus_steal_fn)());
static void deinitialize();
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index fc1b7b74f9..a02354b377 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -79,7 +79,7 @@ public:
for (int i = 0; i < custom_monitor_names.size(); i++) {
Variant monitor_value = performance->call("get_custom_monitor", custom_monitor_names[i]);
if (!monitor_value.is_num()) {
- ERR_PRINT("Value of custom monitor '" + String(custom_monitor_names[i]) + "' is not a number");
+ ERR_PRINT(vformat("Value of custom monitor '%s' is not a number.", String(custom_monitor_names[i])));
arr[i + max] = Variant();
} else {
arr[i + max] = monitor_value;
@@ -569,7 +569,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
if (!captured) {
- WARN_PRINT("Unknown message received from debugger: " + command);
+ WARN_PRINT(vformat("Unknown message received from debugger: %s.", command));
}
}
} else {
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 9dca47a0b4..793db7330f 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -178,7 +178,7 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
+ ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num(tcp_client->get_status())));
return FAILED;
}
connected = true;
diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp
index 813ee7684f..a2369992e6 100644
--- a/core/error/error_macros.cpp
+++ b/core/error/error_macros.cpp
@@ -107,6 +107,28 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
_global_unlock();
}
+// For printing errors when we may crash at any point, so we must flush ASAP a lot of lines
+// but we don't want to make it noisy by printing lots of file & line info (because it's already
+// been printing by a preceding _err_print_error).
+void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type) {
+ if (OS::get_singleton()) {
+ OS::get_singleton()->printerr("ERROR: %s\n", p_error.utf8().get_data());
+ } else {
+ // Fallback if errors happen before OS init or after it's destroyed.
+ const char *err_details = p_error.utf8().get_data();
+ fprintf(stderr, "ERROR: %s\n", err_details);
+ }
+
+ _global_lock();
+ ErrorHandlerList *l = error_handler_list;
+ while (l) {
+ l->errfunc(l->userdata, "", "", 0, p_error.utf8().get_data(), "", false, p_type);
+ l = l->next;
+ }
+
+ _global_unlock();
+}
+
// Errors with message. (All combinations of p_error and p_message as String or char*.)
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
_err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_editor_notify, p_type);
diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index 19c16667d0..752fd605e0 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -68,6 +68,7 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
+void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false);
void _err_flush_stdout();
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index c5f7502c12..7263cafdf3 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -1363,7 +1363,7 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
return true; // May just not have this array and its still good. Probably added recently.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, vformat("New API lacks base array: %s", p_base_array));
Array new_api = p_new_api[p_base_array];
HashMap<String, Dictionary> new_api_assoc;
@@ -1371,6 +1371,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
Dictionary elem = var;
ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field));
String name = elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && elem.has("right_type")) {
name += " " + String(elem["right_type"]);
}
@@ -1386,6 +1389,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
continue;
}
String name = old_elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && old_elem.has("right_type")) {
name += " " + String(old_elem["right_type"]);
}
@@ -1515,7 +1521,7 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered
return true; // May just not have this array and its still good. Probably added recently or optional.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, vformat("New API lacks base array: %s", p_outer));
Array new_api = p_new_api[p_outer];
HashMap<String, Dictionary> new_api_assoc;
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 7cba5cb161..05ba114a96 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -355,8 +355,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
- ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
- ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), vformat("Attempt to register extension class '%s', which is not a valid class identifier.", class_name));
+ ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), vformat("Attempt to register extension class '%s', which appears to be already registered.", class_name));
Extension *parent_extension = nullptr;
@@ -370,7 +370,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
//inheriting from engine class
}
} else {
- ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'.");
+ ERR_FAIL_MSG(vformat("Attempt to register an extension class '%s' using non-existing parent class '%s'.", String(class_name), String(parent_class_name)));
}
#ifdef TOOLS_ENABLED
@@ -463,7 +463,7 @@ void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName method_name = *reinterpret_cast<const StringName *>(p_method_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension method '%s' for unexisting class '%s'.", String(method_name), class_name));
#ifdef TOOLS_ENABLED
Extension *extension = &self->extension_classes[class_name];
@@ -513,7 +513,7 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName enum_name = *reinterpret_cast<const StringName *>(p_enum_name);
StringName constant_name = *reinterpret_cast<const StringName *>(p_constant_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension constant '%s' for unexisting class '%s'.", constant_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -537,7 +537,7 @@ void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLib
StringName setter = *reinterpret_cast<const StringName *>(p_setter);
StringName getter = *reinterpret_cast<const StringName *>(p_getter);
String property_name = *reinterpret_cast<const StringName *>(p_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property '%s' for unexisting class '%s'.", property_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -558,7 +558,7 @@ void GDExtension::_register_extension_class_property_group(GDExtensionClassLibra
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String group_name = *reinterpret_cast<const String *>(p_group_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property group '%s' for unexisting class '%s'.", group_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -577,7 +577,7 @@ void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLi
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String subgroup_name = *reinterpret_cast<const String *>(p_subgroup_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property subgroup '%s' for unexisting class '%s'.", subgroup_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -595,7 +595,7 @@ void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName signal_name = *reinterpret_cast<const StringName *>(p_signal_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class signal '%s' for unexisting class '%s'.", signal_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -618,7 +618,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to unregister unexisting extension class '%s'.", class_name));
Extension *ext = &self->extension_classes[class_name];
#ifdef TOOLS_ENABLED
@@ -626,7 +626,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
self->_clear_extension(ext);
}
#endif
- ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it.");
+ ERR_FAIL_COND_MSG(ext->gdextension.children.size(), vformat("Attempt to unregister class '%s' while other extension classes inherit from it.", class_name));
#ifdef TOOLS_ENABLED
ClassDB::unregister_extension_class(class_name, !ext->is_reloading);
@@ -664,13 +664,13 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte
HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions;
void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
- ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), vformat("Attempt to register interface function '%s', which appears to be already registered.", p_function_name));
gdextension_interface_functions.insert(p_function_name, p_function_pointer);
}
GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) {
GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
- ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + ".");
+ ERR_FAIL_NULL_V_MSG(function, nullptr, vformat("Attempt to get non-existent interface function: '%s'.", String(p_function_name)));
return *function;
}
@@ -680,8 +680,8 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade
Error err = loader->open_library(p_path);
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, vformat("GDExtension dynamic library not found: '%s'.", p_path));
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Can't open GDExtension dynamic library: '%s'.", p_path));
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 91f0b4a2de..203f7960dc 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1526,7 +1526,7 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC
#endif
if (!mb && exists) {
- ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue.");
+ ERR_PRINT(vformat("Method '%s.%s' has changed and no compatibility fallback has been provided. Please open an issue.", classname, methodname));
return nullptr;
}
ERR_FAIL_NULL_V(mb, nullptr);
diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp
index d5f2eb668f..17200916ba 100644
--- a/core/extension/gdextension_library_loader.cpp
+++ b/core/extension/gdextension_library_loader.cpp
@@ -219,7 +219,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false);
if (err != OK) {
- ERR_PRINT("GDExtension entry point '" + entry_symbol + "' not found in library " + library_path);
+ ERR_PRINT(vformat("GDExtension entry point '%s' not found in library %s.", entry_symbol, library_path));
return err;
}
@@ -230,7 +230,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
if (ret) {
return OK;
} else {
- ERR_PRINT("GDExtension initialization function '" + entry_symbol + "' returned an error.");
+ ERR_PRINT(vformat("GDExtension initialization function '%s' returned an error.", entry_symbol));
return FAILED;
}
}
@@ -272,12 +272,12 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
Error err = config->load(p_path);
if (err != OK) {
- ERR_PRINT("Error loading GDExtension configuration file: " + p_path);
+ ERR_PRINT(vformat("Error loading GDExtension configuration file: '%s'.", p_path));
return err;
}
if (!config->has_section_key("configuration", "entry_symbol")) {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
@@ -296,7 +296,7 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
}
}
} else {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index fff938858f..0dc13cf375 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -258,7 +258,7 @@ void GDExtensionManager::load_extensions() {
String s = f->get_line().strip_edges();
if (!s.is_empty()) {
LoadStatus err = load_extension(s);
- ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s);
+ ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
}
}
diff --git a/core/input/input.cpp b/core/input/input.cpp
index eba7ded267..7e2227c729 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -87,11 +87,50 @@ Input *Input::get_singleton() {
void Input::set_mouse_mode(MouseMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ // Allow to be set even if overridden, to see if the platform allows the mode.
set_mouse_mode_func(p_mode);
+ mouse_mode = get_mouse_mode_func();
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ }
}
Input::MouseMode Input::get_mouse_mode() const {
- return get_mouse_mode_func();
+ return mouse_mode;
+}
+
+void Input::set_mouse_mode_override_enabled(bool p_enabled) {
+ if (p_enabled == mouse_mode_override_enabled) {
+ return;
+ }
+
+ mouse_mode_override_enabled = p_enabled;
+
+ if (p_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ mouse_mode_override = get_mouse_mode_func();
+ } else {
+ set_mouse_mode_func(mouse_mode);
+ }
+}
+
+void Input::set_mouse_mode_override(MouseMode p_mode) {
+ ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode_override) {
+ return;
+ }
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(p_mode);
+ mouse_mode_override = get_mouse_mode_func();
+ }
}
void Input::_bind_methods() {
@@ -252,6 +291,10 @@ Input::VelocityTrack::VelocityTrack() {
bool Input::is_anything_pressed() const {
_THREAD_SAFE_METHOD_
+ if (disable_input) {
+ return false;
+ }
+
if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty() || !mouse_button_mask.is_empty()) {
return true;
}
@@ -267,21 +310,41 @@ bool Input::is_anything_pressed() const {
bool Input::is_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return keys_pressed.has(p_keycode);
}
bool Input::is_physical_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return physical_keys_pressed.has(p_keycode);
}
bool Input::is_key_label_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return key_label_pressed.has(p_keycode);
}
bool Input::is_mouse_button_pressed(MouseButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return mouse_button_mask.has_flag(mouse_button_to_mask(p_button));
}
@@ -295,11 +358,21 @@ static JoyButton _combine_device(JoyButton p_value, int p_device) {
bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -310,6 +383,11 @@ bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -331,6 +409,11 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -352,6 +435,11 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -366,6 +454,11 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -410,6 +503,11 @@ Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_po
float Input::get_joy_axis(int p_device, JoyAxis p_axis) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return 0;
+ }
+
JoyAxis c = _combine_device(p_axis, p_device);
if (_joy_axis.has(c)) {
return _joy_axis[c];
@@ -690,6 +788,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
button_event->set_canceled(st->is_canceled());
button_event->set_button_index(MouseButton::LEFT);
button_event->set_double_click(st->is_double_tap());
+ button_event->set_window_id(st->get_window_id());
BitField<MouseButtonMask> ev_bm = mouse_button_mask;
if (st->is_pressed()) {
@@ -727,6 +826,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
motion_event->set_velocity(sd->get_velocity());
motion_event->set_screen_velocity(sd->get_screen_velocity());
motion_event->set_button_mask(mouse_button_mask);
+ motion_event->set_window_id(sd->get_window_id());
_parse_input_event_impl(motion_event, true);
}
@@ -934,7 +1034,7 @@ void Input::action_release(const StringName &p_action) {
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
- action_state.cache.pressed = 0;
+ action_state.cache.pressed = false;
action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0;
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
@@ -1662,6 +1762,14 @@ int Input::get_unused_joy_id() {
return -1;
}
+void Input::set_disable_input(bool p_disable) {
+ disable_input = p_disable;
+}
+
+bool Input::is_input_disabled() const {
+ return disable_input;
+}
+
Input::Input() {
singleton = this;
diff --git a/core/input/input.h b/core/input/input.h
index 95dd623cc0..a189ae7d9a 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -103,6 +103,11 @@ private:
Vector2 mouse_pos;
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;
+ bool disable_input = false;
+
+ MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
+ bool mouse_mode_override_enabled = false;
+ MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
struct ActionState {
uint64_t pressed_physics_frame = UINT64_MAX;
@@ -279,6 +284,8 @@ protected:
public:
void set_mouse_mode(MouseMode p_mode);
MouseMode get_mouse_mode() const;
+ void set_mouse_mode_override_enabled(bool p_enabled);
+ void set_mouse_mode_override(MouseMode p_mode);
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
@@ -380,6 +387,9 @@ public:
void set_event_dispatch_function(EventDispatchFunc p_function);
+ void set_disable_input(bool p_disable);
+ bool is_input_disabled() const;
+
Input();
~Input();
};
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 5b9377fe59..6378f18545 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -114,7 +114,7 @@ void InputMap::get_argument_options(const StringName &p_function, int p_idx, Lis
#endif
void InputMap::add_action(const StringName &p_action, float p_deadzone) {
- ERR_FAIL_COND_MSG(input_map.has(p_action), "InputMap already has action \"" + String(p_action) + "\".");
+ ERR_FAIL_COND_MSG(input_map.has(p_action), vformat("InputMap already has action \"%s\".", String(p_action)));
input_map[p_action] = Action();
static int last_id = 1;
input_map[p_action].id = last_id;
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 2f3fe4deeb..5d2f65ca99 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -177,7 +177,7 @@ Error DirAccess::make_dir_recursive(const String &p_dir) {
curpath = curpath.path_join(subdirs[i]);
Error err = make_dir(curpath);
if (err != OK && err != ERR_ALREADY_EXISTS) {
- ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
+ ERR_FAIL_V_MSG(err, vformat("Could not create directory: '%s'.", curpath));
}
}
@@ -239,7 +239,7 @@ Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
Ref<DirAccess> DirAccess::open(const String &p_path, Error *r_error) {
Ref<DirAccess> da = create_for_path(p_path);
- ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, vformat("Cannot create DirAccess for path '%s'.", p_path));
Error err = da->change_dir(p_path);
if (r_error) {
*r_error = err;
@@ -345,10 +345,10 @@ Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flag
Error err;
{
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_from);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_from));
Ref<FileAccess> fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_to);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_to));
const size_t copy_buffer_limit = 65536; // 64 KB
@@ -444,11 +444,11 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int
String target_dir = p_to + rel_path;
if (!p_target_da->dir_exists(target_dir)) {
Error err = p_target_da->make_dir(target_dir);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", target_dir));
}
Error err = change_dir(rel_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot change current directory to '%s'.", rel_path));
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
if (err) {
@@ -466,11 +466,11 @@ Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags,
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
Ref<DirAccess> target_da = DirAccess::create_for_path(p_to);
- ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_to));
if (!target_da->dir_exists(p_to)) {
Error err = target_da->make_dir_recursive(p_to);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", p_to));
}
if (!p_to.ends_with("/")) {
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index e9c864c56b..2392944f76 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -96,8 +96,8 @@ public:
virtual bool file_exists(String p_file) = 0;
virtual bool dir_exists(String p_dir) = 0;
- virtual bool is_readable(String p_dir) { return true; };
- virtual bool is_writable(String p_dir) { return true; };
+ virtual bool is_readable(String p_dir) { return true; }
+ virtual bool is_writable(String p_dir) { return true; }
static bool exists(const String &p_dir);
virtual uint64_t get_space_left() = 0;
@@ -116,10 +116,10 @@ public:
Ref<DirAccess> da = create(ACCESS_FILESYSTEM);
if (da->file_exists(p_path)) {
if (da->remove(p_path) != OK) {
- ERR_FAIL_MSG("Cannot remove file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove file or directory: '%s'.", p_path));
}
} else {
- ERR_FAIL_MSG("Cannot remove non-existent file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove non-existent file or directory: '%s'.", p_path));
}
}
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index d919243e6b..d8bf645a7d 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -459,7 +459,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
}
Error err = data.resize(p_length);
- ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
+ ERR_FAIL_COND_V_MSG(err != OK, data, vformat("Can't resize data to %d elements.", p_length));
uint8_t *w = data.ptrw();
int64_t len = get_buffer(w, p_length);
@@ -540,7 +540,7 @@ uint64_t FileAccess::get_modified_time(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
uint64_t mt = fa->_get_modified_time(p_file);
return mt;
@@ -552,7 +552,7 @@ BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_unix_permissions(p_file);
}
@@ -563,7 +563,7 @@ Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_unix_permissions(p_file, p_permissions);
return err;
@@ -575,7 +575,7 @@ bool FileAccess::get_hidden_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_hidden_attribute(p_file);
}
@@ -586,7 +586,7 @@ Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_hidden_attribute(p_file, p_hidden);
return err;
@@ -598,7 +598,7 @@ bool FileAccess::get_read_only_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_read_only_attribute(p_file);
}
@@ -609,7 +609,7 @@ Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_read_only_attribute(p_file, p_ro);
return err;
@@ -697,7 +697,7 @@ Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_err
if (r_error) { // if error requested, do not throw error
return Vector<uint8_t>();
}
- ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(Vector<uint8_t>(), vformat("Can't open file from path '%s'.", String(p_path)));
}
Vector<uint8_t> data;
data.resize(f->get_length());
@@ -715,7 +715,7 @@ String FileAccess::get_file_as_string(const String &p_path, Error *r_error) {
if (r_error) {
return String();
}
- ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(String(), vformat("Can't get file as string from path '%s'.", String(p_path)));
}
String ret;
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 2f4d1a8604..7f5687fe03 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -215,8 +215,8 @@ public:
static bool get_read_only_attribute(const String &p_file);
static Error set_read_only_attribute(const String &p_file, bool p_ro);
- static void set_backup_save(bool p_enable) { backup_save = p_enable; };
- static bool is_backup_save_enabled() { return backup_save; };
+ static void set_backup_save(bool p_enable) { backup_save = p_enable; }
+ static bool is_backup_save_enabled() { return backup_save; }
static String get_md5(const String &p_file);
static String get_sha256(const String &p_file);
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 3602baf8c5..f7f2852e0a 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -58,7 +58,7 @@ Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {
block_size = f->get_32();
if (block_size == 0) {
f.unref();
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted.");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Can't open compressed file '%s' with block size 0, it is corrupted.", p_base->get_path()));
}
read_total = f->get_32();
uint32_t bc = (read_total / block_size) + 1;
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index 13d1e0c8fc..24be9ef230 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -37,7 +37,7 @@
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
- ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
+ ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, vformat("Can't open file while another file from path '%s' is open.", file->get_path_absolute()));
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 1541a5ed4a..8d74011632 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -85,7 +85,7 @@ Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
//name = DirAccess::normalize_path(name);
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
- ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, "Can't find file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", p_path));
data = E->value.ptrw();
length = E->value.size();
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 1340382eaa..b9af1bfb57 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -48,7 +48,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
}
void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) {
- String simplified_path = p_path.simplify_path();
+ String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
bool exists = files.has(pmd5);
@@ -68,13 +68,11 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
if (!exists) {
- //search for dir
- String p = simplified_path.replace_first("res://", "");
+ // Search for directory.
PackedDir *cd = root;
- if (p.contains("/")) { //in a subdir
-
- Vector<String> ds = p.get_base_dir().split("/");
+ if (simplified_path.contains("/")) { // In a subdirectory.
+ Vector<String> ds = simplified_path.get_base_dir().split("/");
for (int j = 0; j < ds.size(); j++) {
if (!cd->subdirs.has(ds[j])) {
@@ -89,13 +87,40 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
}
String filename = simplified_path.get_file();
- // Don't add as a file if the path points to a directory
+ // Don't add as a file if the path points to a directory.
if (!filename.is_empty()) {
cd->files.insert(filename);
}
}
}
+void PackedData::remove_path(const String &p_path) {
+ String simplified_path = p_path.simplify_path().trim_prefix("res://");
+ PathMD5 pmd5(simplified_path.md5_buffer());
+ if (!files.has(pmd5)) {
+ return;
+ }
+
+ // Search for directory.
+ PackedDir *cd = root;
+
+ if (simplified_path.contains("/")) { // In a subdirectory.
+ Vector<String> ds = simplified_path.get_base_dir().split("/");
+
+ for (int j = 0; j < ds.size(); j++) {
+ if (!cd->subdirs.has(ds[j])) {
+ return; // Subdirectory does not exist, do not bother creating.
+ } else {
+ cd = cd->subdirs[ds[j]];
+ }
+ }
+ }
+
+ cd->files.erase(simplified_path.get_file());
+
+ files.erase(pmd5);
+}
+
void PackedData::add_pack_source(PackSource *p_source) {
if (p_source != nullptr) {
sources.push_back(p_source);
@@ -103,15 +128,32 @@ void PackedData::add_pack_source(PackSource *p_source) {
}
uint8_t *PackedData::get_file_hash(const String &p_path) {
- PathMD5 pmd5(p_path.md5_buffer());
+ String simplified_path = p_path.simplify_path().trim_prefix("res://");
+ PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
- if (!E || E->value.offset == 0) {
+ if (!E) {
return nullptr;
}
return E->value.md5;
}
+HashSet<String> PackedData::get_file_paths() const {
+ HashSet<String> file_paths;
+ _get_file_paths(root, root->name, file_paths);
+ return file_paths;
+}
+
+void PackedData::_get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const {
+ for (const String &E : p_dir->files) {
+ r_paths.insert(p_parent_dir.path_join(E));
+ }
+
+ for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) {
+ _get_file_paths(E.value, p_parent_dir.path_join(E.key), r_paths);
+ }
+}
+
void PackedData::clear() {
files.clear();
_free_packed_dirs(root);
@@ -223,8 +265,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
uint32_t ver_minor = f->get_32();
f->get_32(); // patch number, not used for validation.
- ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + ".");
- ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
+ ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, vformat("Pack version unsupported: %d.", version));
+ ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.", ver_major, ver_minor));
uint32_t pack_flags = f->get_32();
uint64_t file_base = f->get_64();
@@ -269,13 +311,17 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
String path;
path.parse_utf8(cs.ptr());
- uint64_t ofs = file_base + f->get_64();
+ uint64_t ofs = f->get_64();
uint64_t size = f->get_64();
uint8_t md5[16];
f->get_buffer(md5, 16);
uint32_t flags = f->get_32();
- PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
+ if (flags & PACK_FILE_REMOVAL) { // The file was removed.
+ PackedData::get_singleton()->remove_path(path);
+ } else {
+ PackedData::get_singleton()->add_path(p_path, path, file_base + ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
+ }
}
return true;
@@ -386,7 +432,7 @@ void FileAccessPack::close() {
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) :
pf(p_file),
f(FileAccess::open(pf.pack, FileAccess::READ)) {
- ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
f->seek(pf.offset);
off = pf.offset;
@@ -394,7 +440,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
if (pf.encrypted) {
Ref<FileAccessEncrypted> fae;
fae.instantiate();
- ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
Vector<uint8_t> key;
key.resize(32);
@@ -403,7 +449,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
}
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
- ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
f = fae;
off = 0;
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 57b7a5f87f..bf44b3a2db 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -49,7 +49,8 @@ enum PackFlags {
};
enum PackFileFlags {
- PACK_FILE_ENCRYPTED = 1 << 0
+ PACK_FILE_ENCRYPTED = 1 << 0,
+ PACK_FILE_REMOVAL = 1 << 1,
};
class PackSource;
@@ -107,11 +108,14 @@ private:
bool disabled = false;
void _free_packed_dirs(PackedDir *p_dir);
+ void _get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const;
public:
void add_pack_source(PackSource *p_source);
void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource
+ void remove_path(const String &p_path);
uint8_t *get_file_hash(const String &p_path);
+ HashSet<String> get_file_paths() const;
void set_disabled(bool p_disabled) { disabled = p_disabled; }
_FORCE_INLINE_ bool is_disabled() const { return disabled; }
@@ -190,14 +194,11 @@ public:
};
Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
- String simplified_path = p_path.simplify_path();
+ String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
if (!E) {
- return nullptr; //not found
- }
- if (E->value.offset == 0) {
- return nullptr; //was erased
+ return nullptr; // Not found.
}
return E->value.src->get_file(p_path, &E->value);
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index b33b7b35c3..41907d1a3f 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -116,7 +116,7 @@ void ZipArchive::close_handle(unzFile p_file) const {
}
unzFile ZipArchive::get_file_handle(const String &p_file) const {
- ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
+ ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, vformat("File '%s' doesn't exist.", p_file));
File file = files[p_file];
zlib_filefunc_def io;
@@ -136,7 +136,7 @@ unzFile ZipArchive::get_file_handle(const String &p_file) const {
io.free_mem = godot_free;
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
- ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
+ ERR_FAIL_NULL_V_MSG(pkg, nullptr, vformat("Cannot open file '%s'.", packages[file.package].filename));
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
unzClose(pkg);
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index fc91341bed..9e426c4908 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -100,9 +100,9 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
Error HTTPClient::verify_headers(const Vector<String> &p_headers) {
for (int i = 0; i < p_headers.size(); i++) {
String sanitized = p_headers[i].strip_edges();
- ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty.");
+ ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, vformat("Invalid HTTP header at index %d: empty.", i));
ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER,
- "Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]);
+ vformat("Invalid HTTP header at index %d: String must contain header-value pair, delimited by ':', but was: '%s'.", i, p_headers[i]));
}
return OK;
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index 70fcad543a..237ba30a80 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -662,15 +662,16 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() {
chunk_left -= rec;
if (chunk_left == 0) {
- if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
+ const int chunk_size = chunk.size();
+ if (chunk[chunk_size - 2] != '\r' || chunk[chunk_size - 1] != '\n') {
ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
status = STATUS_CONNECTION_ERROR;
break;
}
- ret.resize(chunk.size() - 2);
+ ret.resize(chunk_size - 2);
uint8_t *w = ret.ptrw();
- memcpy(w, chunk.ptr(), chunk.size() - 2);
+ memcpy(w, chunk.ptr(), chunk_size - 2);
chunk.clear();
}
diff --git a/core/io/image.cpp b/core/io/image.cpp
index d782af931f..fbf37cbee7 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -44,24 +44,24 @@
#include <cmath>
const char *Image::format_names[Image::FORMAT_MAX] = {
- "Lum8", //luminance
- "LumAlpha8", //luminance-alpha
+ "Lum8",
+ "LumAlpha8",
"Red8",
"RedGreen",
"RGB8",
"RGBA8",
"RGBA4444",
- "RGBA5551",
- "RFloat", //float
+ "RGBA5551", // Actually RGB565, kept as RGBA5551 for compatibility.
+ "RFloat",
"RGFloat",
"RGBFloat",
"RGBAFloat",
- "RHalf", //half float
+ "RHalf",
"RGHalf",
"RGBHalf",
"RGBAHalf",
"RGBE9995",
- "DXT1 RGB8", //s3tc
+ "DXT1 RGB8",
"DXT3 RGBA8",
"DXT5 RGBA8",
"RGTC Red8",
@@ -69,9 +69,9 @@ const char *Image::format_names[Image::FORMAT_MAX] = {
"BPTC_RGBA",
"BPTC_RGBF",
"BPTC_RGBFU",
- "ETC", //etc1
- "ETC2_R11", //etc2
- "ETC2_R11S", //signed", NOT srgb.
+ "ETC",
+ "ETC2_R11",
+ "ETC2_R11S",
"ETC2_RG11",
"ETC2_RG11S",
"ETC2_RGB8",
@@ -85,17 +85,60 @@ const char *Image::format_names[Image::FORMAT_MAX] = {
"ASTC_8x8_HDR",
};
+// External saver function pointers.
+
SavePNGFunc Image::save_png_func = nullptr;
SaveJPGFunc Image::save_jpg_func = nullptr;
SaveEXRFunc Image::save_exr_func = nullptr;
+SaveWebPFunc Image::save_webp_func = nullptr;
SavePNGBufferFunc Image::save_png_buffer_func = nullptr;
-SaveEXRBufferFunc Image::save_exr_buffer_func = nullptr;
SaveJPGBufferFunc Image::save_jpg_buffer_func = nullptr;
-
-SaveWebPFunc Image::save_webp_func = nullptr;
+SaveEXRBufferFunc Image::save_exr_buffer_func = nullptr;
SaveWebPBufferFunc Image::save_webp_buffer_func = nullptr;
+// External loader function pointers.
+
+ImageMemLoadFunc Image::_png_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_png_mem_unpacker_func = nullptr;
+ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr;
+ScalableImageMemLoadFunc Image::_svg_scalable_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_ktx_mem_loader_func = nullptr;
+
+// External VRAM compression function pointers.
+
+void (*Image::_image_compress_bc_func)(Image *, Image::UsedChannels) = nullptr;
+void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr;
+void (*Image::_image_compress_etc1_func)(Image *) = nullptr;
+void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr;
+void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr;
+
+Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr;
+Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr;
+
+// External VRAM decompression function pointers.
+
+void (*Image::_image_decompress_bc)(Image *) = nullptr;
+void (*Image::_image_decompress_bptc)(Image *) = nullptr;
+void (*Image::_image_decompress_etc1)(Image *) = nullptr;
+void (*Image::_image_decompress_etc2)(Image *) = nullptr;
+void (*Image::_image_decompress_astc)(Image *) = nullptr;
+
+// External packer function pointers.
+
+Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
+Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
+Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
+Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr;
+
+Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
+Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
+Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr;
+Ref<Image> (*Image::basis_universal_unpacker_ptr)(const uint8_t *, int) = nullptr;
+
void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel) {
uint32_t ofs = (p_y * width + p_x) * p_pixel_size;
memcpy(p_data + ofs, p_pixel, p_pixel_size);
@@ -109,9 +152,9 @@ void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *
int Image::get_format_pixel_size(Format p_format) {
switch (p_format) {
case FORMAT_L8:
- return 1; //luminance
+ return 1;
case FORMAT_LA8:
- return 2; //luminance-alpha
+ return 2;
case FORMAT_R8:
return 1;
case FORMAT_RG8:
@@ -125,7 +168,7 @@ int Image::get_format_pixel_size(Format p_format) {
case FORMAT_RGB565:
return 2;
case FORMAT_RF:
- return 4; //float
+ return 4;
case FORMAT_RGF:
return 8;
case FORMAT_RGBF:
@@ -133,7 +176,7 @@ int Image::get_format_pixel_size(Format p_format) {
case FORMAT_RGBAF:
return 16;
case FORMAT_RH:
- return 2; //half float
+ return 2;
case FORMAT_RGH:
return 4;
case FORMAT_RGBH:
@@ -143,27 +186,27 @@ int Image::get_format_pixel_size(Format p_format) {
case FORMAT_RGBE9995:
return 4;
case FORMAT_DXT1:
- return 1; //s3tc bc1
+ return 1;
case FORMAT_DXT3:
- return 1; //bc2
+ return 1;
case FORMAT_DXT5:
- return 1; //bc3
+ return 1;
case FORMAT_RGTC_R:
- return 1; //bc4
+ return 1;
case FORMAT_RGTC_RG:
- return 1; //bc5
+ return 1;
case FORMAT_BPTC_RGBA:
- return 1; //btpc bc6h
+ return 1;
case FORMAT_BPTC_RGBF:
- return 1; //float /
+ return 1;
case FORMAT_BPTC_RGBFU:
- return 1; //unsigned float
+ return 1;
case FORMAT_ETC:
- return 1; //etc1
+ return 1;
case FORMAT_ETC2_R11:
- return 1; //etc2
+ return 1;
case FORMAT_ETC2_R11S:
- return 1; //signed: return 1; NOT srgb.
+ return 1;
case FORMAT_ETC2_RG11:
return 1;
case FORMAT_ETC2_RG11S:
@@ -194,12 +237,11 @@ int Image::get_format_pixel_size(Format p_format) {
void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) {
switch (p_format) {
- case FORMAT_DXT1: //s3tc bc1
- case FORMAT_DXT3: //bc2
- case FORMAT_DXT5: //bc3
- case FORMAT_RGTC_R: //bc4
- case FORMAT_RGTC_RG: { //bc5 case case FORMAT_DXT1:
-
+ case FORMAT_DXT1:
+ case FORMAT_DXT3:
+ case FORMAT_DXT5:
+ case FORMAT_RGTC_R:
+ case FORMAT_RGTC_RG: {
r_w = 4;
r_h = 4;
} break;
@@ -213,8 +255,8 @@ void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) {
r_w = 4;
r_h = 4;
} break;
- case FORMAT_ETC2_R11: //etc2
- case FORMAT_ETC2_R11S: //signed: NOT srgb.
+ case FORMAT_ETC2_R11:
+ case FORMAT_ETC2_R11S:
case FORMAT_ETC2_RG11:
case FORMAT_ETC2_RG11S:
case FORMAT_ETC2_RGB8:
@@ -224,19 +266,16 @@ void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) {
case FORMAT_DXT5_RA_AS_RG: {
r_w = 4;
r_h = 4;
-
} break;
case FORMAT_ASTC_4x4:
case FORMAT_ASTC_4x4_HDR: {
r_w = 4;
r_h = 4;
-
} break;
case FORMAT_ASTC_8x8:
case FORMAT_ASTC_8x8_HDR: {
r_w = 8;
r_h = 8;
-
} break;
default: {
r_w = 1;
@@ -257,12 +296,11 @@ int Image::get_format_pixel_rshift(Format p_format) {
int Image::get_format_block_size(Format p_format) {
switch (p_format) {
- case FORMAT_DXT1: //s3tc bc1
- case FORMAT_DXT3: //bc2
- case FORMAT_DXT5: //bc3
- case FORMAT_RGTC_R: //bc4
- case FORMAT_RGTC_RG: { //bc5 case case FORMAT_DXT1:
-
+ case FORMAT_DXT1:
+ case FORMAT_DXT3:
+ case FORMAT_DXT5:
+ case FORMAT_RGTC_R:
+ case FORMAT_RGTC_RG: {
return 4;
}
case FORMAT_ETC: {
@@ -273,17 +311,15 @@ int Image::get_format_block_size(Format p_format) {
case FORMAT_BPTC_RGBFU: {
return 4;
}
- case FORMAT_ETC2_R11: //etc2
- case FORMAT_ETC2_R11S: //signed: NOT srgb.
+ case FORMAT_ETC2_R11:
+ case FORMAT_ETC2_R11S:
case FORMAT_ETC2_RG11:
case FORMAT_ETC2_RG11S:
case FORMAT_ETC2_RGB8:
case FORMAT_ETC2_RGBA8:
case FORMAT_ETC2_RGB8A1:
- case FORMAT_ETC2_RA_AS_RG: //used to make basis universal happy
- case FORMAT_DXT5_RA_AS_RG: //used to make basis universal happy
-
- {
+ case FORMAT_ETC2_RA_AS_RG:
+ case FORMAT_DXT5_RA_AS_RG: {
return 4;
}
case FORMAT_ASTC_4x4:
@@ -459,7 +495,7 @@ int Image::get_mipmap_count() const {
}
}
-//using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers
+// Using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers.
template <uint32_t read_bytes, bool read_alpha, uint32_t write_bytes, bool write_alpha, bool read_gray, bool write_gray>
static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) {
constexpr uint32_t max_bytes = MAX(read_bytes, write_bytes);
@@ -535,7 +571,7 @@ static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_for
}
void Image::convert(Format p_new_format) {
- ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum.");
+ ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_new_format));
if (data.size() == 0) {
return;
}
@@ -551,7 +587,7 @@ void Image::convert(Format p_new_format) {
ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead.");
} else if (!_are_formats_compatible(format, p_new_format)) {
- //use put/set pixel which is slower but works with non byte formats
+ // Use put/set pixel which is slower but works with non-byte formats.
Image new_img(width, height, mipmaps, p_new_format);
for (int mip = 0; mip < mipmap_count; mip++) {
@@ -1132,9 +1168,9 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0.");
- ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + ".");
- ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
+ ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, vformat("Image width cannot be greater than %d pixels.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, vformat("Image height cannot be greater than %d pixels.", MAX_HEIGHT));
+ ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, vformat("Too many pixels for image, maximum is %d pixels.", MAX_PIXELS));
if (p_width == width && p_height == height) {
return;
@@ -1435,8 +1471,8 @@ void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) {
ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0.");
ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0.");
- ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + itos(MAX_HEIGHT) + ".");
+ ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, vformat("End x position cannot be greater than %d.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, vformat("End y position cannot be greater than %d.", MAX_HEIGHT));
/* to save memory, cropping should be done in-place, however, since this function
will most likely either not be used much, or in critical areas, for now it won't, because
@@ -1484,8 +1520,8 @@ void Image::crop(int p_width, int p_height) {
void Image::rotate_90(ClockDirection p_direction) {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -1602,8 +1638,8 @@ void Image::rotate_90(ClockDirection p_direction) {
void Image::rotate_180() {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -1694,7 +1730,7 @@ void Image::flip_x() {
}
}
-/// Get mipmap size and offset.
+// Get mipmap size and offset.
int64_t Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) {
// Data offset in mipmaps (including the original texture).
int64_t size = 0;
@@ -2249,15 +2285,15 @@ void Image::set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_for
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + "pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + "pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels.");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm = 0;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2275,15 +2311,15 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + " pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + " pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels .");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2577,7 +2613,7 @@ Image::AlphaMode Image::detect_alpha() const {
Error Image::load(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
return ImageLoader::load_image(p_path, this);
@@ -2586,7 +2622,7 @@ Error Image::load(const String &p_path) {
Ref<Image> Image::load_from_file(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
Ref<Image> image;
@@ -2649,7 +2685,7 @@ Error Image::save_webp(const String &p_path, const bool p_lossy, const float p_q
if (save_webp_func == nullptr) {
return ERR_UNAVAILABLE;
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_func(p_path, Ref<Image>((Image *)this), p_lossy, p_quality);
}
@@ -2658,7 +2694,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua
if (save_webp_buffer_func == nullptr) {
return Vector<uint8_t>();
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality);
}
@@ -3134,37 +3170,6 @@ void Image::fill_rect(const Rect2i &p_rect, const Color &p_color) {
}
}
-ImageMemLoadFunc Image::_png_mem_loader_func = nullptr;
-ImageMemLoadFunc Image::_png_mem_unpacker_func = nullptr;
-ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr;
-ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr;
-ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr;
-ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr;
-ScalableImageMemLoadFunc Image::_svg_scalable_mem_loader_func = nullptr;
-ImageMemLoadFunc Image::_ktx_mem_loader_func = nullptr;
-
-void (*Image::_image_compress_bc_func)(Image *, Image::UsedChannels) = nullptr;
-void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr;
-void (*Image::_image_compress_etc1_func)(Image *) = nullptr;
-void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr;
-void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr;
-Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr;
-Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr;
-void (*Image::_image_decompress_bc)(Image *) = nullptr;
-void (*Image::_image_decompress_bptc)(Image *) = nullptr;
-void (*Image::_image_decompress_etc1)(Image *) = nullptr;
-void (*Image::_image_decompress_etc2)(Image *) = nullptr;
-void (*Image::_image_decompress_astc)(Image *) = nullptr;
-
-Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
-Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
-Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
-Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
-Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
-Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr;
-Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr;
-Ref<Image> (*Image::basis_universal_unpacker_ptr)(const uint8_t *, int) = nullptr;
-
void Image::_set_data(const Dictionary &p_data) {
ERR_FAIL_COND(!p_data.has("width"));
ERR_FAIL_COND(!p_data.has("height"));
@@ -3204,6 +3209,14 @@ Color Image::get_pixelv(const Point2i &p_point) const {
return get_pixel(p_point.x, p_point.y);
}
+void Image::_copy_internals_from(const Image &p_image) {
+ format = p_image.format;
+ width = p_image.width;
+ height = p_image.height;
+ mipmaps = p_image.mipmaps;
+ data = p_image.data;
+}
+
Color Image::_get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const {
switch (format) {
case FORMAT_L8: {
@@ -3643,34 +3656,34 @@ void Image::_bind_methods() {
BIND_CONSTANT(MAX_WIDTH);
BIND_CONSTANT(MAX_HEIGHT);
- BIND_ENUM_CONSTANT(FORMAT_L8); //luminance
- BIND_ENUM_CONSTANT(FORMAT_LA8); //luminance-alpha
+ BIND_ENUM_CONSTANT(FORMAT_L8);
+ BIND_ENUM_CONSTANT(FORMAT_LA8);
BIND_ENUM_CONSTANT(FORMAT_R8);
BIND_ENUM_CONSTANT(FORMAT_RG8);
BIND_ENUM_CONSTANT(FORMAT_RGB8);
BIND_ENUM_CONSTANT(FORMAT_RGBA8);
BIND_ENUM_CONSTANT(FORMAT_RGBA4444);
BIND_ENUM_CONSTANT(FORMAT_RGB565);
- BIND_ENUM_CONSTANT(FORMAT_RF); //float
+ BIND_ENUM_CONSTANT(FORMAT_RF);
BIND_ENUM_CONSTANT(FORMAT_RGF);
BIND_ENUM_CONSTANT(FORMAT_RGBF);
BIND_ENUM_CONSTANT(FORMAT_RGBAF);
- BIND_ENUM_CONSTANT(FORMAT_RH); //half float
+ BIND_ENUM_CONSTANT(FORMAT_RH);
BIND_ENUM_CONSTANT(FORMAT_RGH);
BIND_ENUM_CONSTANT(FORMAT_RGBH);
BIND_ENUM_CONSTANT(FORMAT_RGBAH);
BIND_ENUM_CONSTANT(FORMAT_RGBE9995);
- BIND_ENUM_CONSTANT(FORMAT_DXT1); //s3tc bc1
- BIND_ENUM_CONSTANT(FORMAT_DXT3); //bc2
- BIND_ENUM_CONSTANT(FORMAT_DXT5); //bc3
+ BIND_ENUM_CONSTANT(FORMAT_DXT1);
+ BIND_ENUM_CONSTANT(FORMAT_DXT3);
+ BIND_ENUM_CONSTANT(FORMAT_DXT5);
BIND_ENUM_CONSTANT(FORMAT_RGTC_R);
BIND_ENUM_CONSTANT(FORMAT_RGTC_RG);
- BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBA); //btpc bc6h
- BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBF); //float /
- BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBFU); //unsigned float
- BIND_ENUM_CONSTANT(FORMAT_ETC); //etc1
- BIND_ENUM_CONSTANT(FORMAT_ETC2_R11); //etc2
- BIND_ENUM_CONSTANT(FORMAT_ETC2_R11S); //signed ); NOT srgb.
+ BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBA);
+ BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBF);
+ BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBFU);
+ BIND_ENUM_CONSTANT(FORMAT_ETC);
+ BIND_ENUM_CONSTANT(FORMAT_ETC2_R11);
+ BIND_ENUM_CONSTANT(FORMAT_ETC2_R11S);
BIND_ENUM_CONSTANT(FORMAT_ETC2_RG11);
BIND_ENUM_CONSTANT(FORMAT_ETC2_RG11S);
BIND_ENUM_CONSTANT(FORMAT_ETC2_RGB8);
@@ -4177,7 +4190,7 @@ void Image::renormalize_half(uint16_t *p_rgb) {
}
void Image::renormalize_rgbe9995(uint32_t *p_rgb) {
- // Never used
+ // Never used.
}
Image::Image(const uint8_t *p_mem_png_jpg, int p_len) {
@@ -4210,6 +4223,15 @@ void Image::set_as_black() {
memset(data.ptrw(), 0, data.size());
}
+void Image::copy_internals_from(const Ref<Image> &p_image) {
+ ERR_FAIL_COND_MSG(p_image.is_null(), "Cannot copy image internals: invalid Image object.");
+ format = p_image->format;
+ width = p_image->width;
+ height = p_image->height;
+ mipmaps = p_image->mipmaps;
+ data = p_image->data;
+}
+
Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool p_luma_metric) {
// https://github.com/richgel999/bc7enc_rdo/blob/master/LICENSE
//
@@ -4250,8 +4272,6 @@ Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool
}
ERR_FAIL_COND_V(err != OK, result);
- ERR_FAIL_COND_V(err != OK, result);
-
ERR_FAIL_COND_V_MSG((compared_image->get_format() >= Image::FORMAT_RH) && (compared_image->get_format() <= Image::FORMAT_RGBE9995), result, "Metrics on HDR images are not supported.");
ERR_FAIL_COND_V_MSG((source_image->get_format() >= Image::FORMAT_RH) && (source_image->get_format() <= Image::FORMAT_RGBE9995), result, "Metrics on HDR images are not supported.");
diff --git a/core/io/image.h b/core/io/image.h
index 78757246e0..3149314ad8 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -43,12 +43,17 @@
class Image;
+// Function pointer prototypes.
+
typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img);
typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img);
+
typedef Error (*SaveJPGFunc)(const String &p_path, const Ref<Image> &p_img, float p_quality);
typedef Vector<uint8_t> (*SaveJPGBufferFunc)(const Ref<Image> &p_img, float p_quality);
+
typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
typedef Ref<Image> (*ScalableImageMemLoadFunc)(const uint8_t *p_data, int p_size, float p_scale);
+
typedef Error (*SaveWebPFunc)(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
typedef Vector<uint8_t> (*SaveWebPBufferFunc)(const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
@@ -59,57 +64,48 @@ class Image : public Resource {
GDCLASS(Image, Resource);
public:
- static SavePNGFunc save_png_func;
- static SaveJPGFunc save_jpg_func;
- static SaveEXRFunc save_exr_func;
- static SavePNGBufferFunc save_png_buffer_func;
- static SaveEXRBufferFunc save_exr_buffer_func;
- static SaveJPGBufferFunc save_jpg_buffer_func;
- static SaveWebPFunc save_webp_func;
- static SaveWebPBufferFunc save_webp_buffer_func;
-
enum {
- MAX_WIDTH = (1 << 24), // force a limit somehow
- MAX_HEIGHT = (1 << 24), // force a limit somehow
- MAX_PIXELS = 268435456
+ MAX_WIDTH = (1 << 24), // Force a limit somehow.
+ MAX_HEIGHT = (1 << 24), // Force a limit somehow.
+ MAX_PIXELS = 268435456 // 16384 ^ 2
};
enum Format {
- FORMAT_L8, //luminance
- FORMAT_LA8, //luminance-alpha
+ FORMAT_L8, // Luminance
+ FORMAT_LA8, // Luminance-Alpha
FORMAT_R8,
FORMAT_RG8,
FORMAT_RGB8,
FORMAT_RGBA8,
FORMAT_RGBA4444,
FORMAT_RGB565,
- FORMAT_RF, //float
+ FORMAT_RF, // Float
FORMAT_RGF,
FORMAT_RGBF,
FORMAT_RGBAF,
- FORMAT_RH, //half float
+ FORMAT_RH, // Half
FORMAT_RGH,
FORMAT_RGBH,
FORMAT_RGBAH,
FORMAT_RGBE9995,
- FORMAT_DXT1, //s3tc bc1
- FORMAT_DXT3, //bc2
- FORMAT_DXT5, //bc3
- FORMAT_RGTC_R,
- FORMAT_RGTC_RG,
- FORMAT_BPTC_RGBA, //btpc bc7
- FORMAT_BPTC_RGBF, //float bc6h
- FORMAT_BPTC_RGBFU, //unsigned float bc6hu
- FORMAT_ETC, //etc1
- FORMAT_ETC2_R11, //etc2
- FORMAT_ETC2_R11S, //signed, NOT srgb.
+ FORMAT_DXT1, // BC1
+ FORMAT_DXT3, // BC2
+ FORMAT_DXT5, // BC3
+ FORMAT_RGTC_R, // BC4
+ FORMAT_RGTC_RG, // BC5
+ FORMAT_BPTC_RGBA, // BC7
+ FORMAT_BPTC_RGBF, // BC6 Signed
+ FORMAT_BPTC_RGBFU, // BC6 Unsigned
+ FORMAT_ETC, // ETC1
+ FORMAT_ETC2_R11,
+ FORMAT_ETC2_R11S, // Signed, NOT srgb.
FORMAT_ETC2_RG11,
- FORMAT_ETC2_RG11S,
+ FORMAT_ETC2_RG11S, // Signed, NOT srgb.
FORMAT_ETC2_RGB8,
FORMAT_ETC2_RGBA8,
FORMAT_ETC2_RGB8A1,
- FORMAT_ETC2_RA_AS_RG, //used to make basis universal happy
- FORMAT_DXT5_RA_AS_RG, //used to make basis universal happy
+ FORMAT_ETC2_RA_AS_RG, // ETC2 RGBA with a RA-RG swizzle for normal maps.
+ FORMAT_DXT5_RA_AS_RG, // BC3 with a RA-RG swizzle for normal maps.
FORMAT_ASTC_4x4,
FORMAT_ASTC_4x4_HDR,
FORMAT_ASTC_8x8,
@@ -118,17 +114,18 @@ public:
};
static const char *format_names[FORMAT_MAX];
+
enum Interpolation {
INTERPOLATE_NEAREST,
INTERPOLATE_BILINEAR,
INTERPOLATE_CUBIC,
INTERPOLATE_TRILINEAR,
INTERPOLATE_LANCZOS,
- /* INTERPOLATE_TRICUBIC, */
- /* INTERPOLATE GAUSS */
+ // INTERPOLATE_TRICUBIC,
+ // INTERPOLATE_GAUSS
};
- //this is used for compression
+ // Used for obtaining optimal compression quality.
enum UsedChannels {
USED_CHANNELS_L,
USED_CHANNELS_LA,
@@ -137,13 +134,66 @@ public:
USED_CHANNELS_RGB,
USED_CHANNELS_RGBA,
};
- //some functions provided by something else
+ // ASTC supports block formats other than 4x4.
enum ASTCFormat {
ASTC_FORMAT_4x4,
ASTC_FORMAT_8x8,
};
+ enum RoughnessChannel {
+ ROUGHNESS_CHANNEL_R,
+ ROUGHNESS_CHANNEL_G,
+ ROUGHNESS_CHANNEL_B,
+ ROUGHNESS_CHANNEL_A,
+ ROUGHNESS_CHANNEL_L,
+ };
+
+ enum Image3DValidateError {
+ VALIDATE_3D_OK,
+ VALIDATE_3D_ERR_IMAGE_EMPTY,
+ VALIDATE_3D_ERR_MISSING_IMAGES,
+ VALIDATE_3D_ERR_EXTRA_IMAGES,
+ VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH,
+ VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH,
+ VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS,
+ };
+
+ enum CompressMode {
+ COMPRESS_S3TC,
+ COMPRESS_ETC,
+ COMPRESS_ETC2,
+ COMPRESS_BPTC,
+ COMPRESS_ASTC,
+ COMPRESS_MAX,
+ };
+
+ enum CompressSource {
+ COMPRESS_SOURCE_GENERIC,
+ COMPRESS_SOURCE_SRGB,
+ COMPRESS_SOURCE_NORMAL,
+ COMPRESS_SOURCE_MAX,
+ };
+
+ enum AlphaMode {
+ ALPHA_NONE,
+ ALPHA_BIT,
+ ALPHA_BLEND
+ };
+
+ // External saver function pointers.
+
+ static SavePNGFunc save_png_func;
+ static SaveJPGFunc save_jpg_func;
+ static SaveEXRFunc save_exr_func;
+ static SaveWebPFunc save_webp_func;
+ static SavePNGBufferFunc save_png_buffer_func;
+ static SaveEXRBufferFunc save_exr_buffer_func;
+ static SaveJPGBufferFunc save_jpg_buffer_func;
+ static SaveWebPBufferFunc save_webp_buffer_func;
+
+ // External loader function pointers.
+
static ImageMemLoadFunc _png_mem_loader_func;
static ImageMemLoadFunc _png_mem_unpacker_func;
static ImageMemLoadFunc _jpg_mem_loader_func;
@@ -153,6 +203,8 @@ public:
static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func;
static ImageMemLoadFunc _ktx_mem_loader_func;
+ // External VRAM compression function pointers.
+
static void (*_image_compress_bc_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_bptc_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_etc1_func)(Image *);
@@ -162,24 +214,26 @@ public:
static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels);
static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels);
+ // External VRAM decompression function pointers.
+
static void (*_image_decompress_bc)(Image *);
static void (*_image_decompress_bptc)(Image *);
static void (*_image_decompress_etc1)(Image *);
static void (*_image_decompress_etc2)(Image *);
static void (*_image_decompress_astc)(Image *);
+ // External packer function pointers.
+
static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
static Vector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image);
- static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*png_packer)(const Ref<Image> &p_image);
- static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels);
+
+ static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
+ static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size);
- _FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const;
- _FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color);
-
protected:
static void _bind_methods();
@@ -190,15 +244,12 @@ private:
int height = 0;
bool mipmaps = false;
- void _copy_internals_from(const Image &p_image) {
- format = p_image.format;
- width = p_image.width;
- height = p_image.height;
- mipmaps = p_image.mipmaps;
- data = p_image.data;
- }
+ void _copy_internals_from(const Image &p_image);
+
+ _FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const;
+ _FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color);
- _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data
+ _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; // Get where the mipmap begins in data.
static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr);
bool _can_modify(Format p_format) const;
@@ -225,52 +276,32 @@ private:
static void renormalize_rgbe9995(uint32_t *p_rgb);
public:
- int get_width() const; ///< Get image width
- int get_height() const; ///< Get image height
+ int get_width() const;
+ int get_height() const;
Size2i get_size() const;
bool has_mipmaps() const;
int get_mipmap_count() const;
- /**
- * Convert the image to another format, conversion only to raw byte format
- */
+ // Convert the image to another format, conversion only to raw byte format.
void convert(Format p_new_format);
- /**
- * Get the current image format.
- */
Format get_format() const;
- /**
- * Get where the mipmap begins in data.
- */
+ // Get where the mipmap begins in data.
int64_t get_mipmap_offset(int p_mipmap) const;
void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const;
void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const;
- enum Image3DValidateError {
- VALIDATE_3D_OK,
- VALIDATE_3D_ERR_IMAGE_EMPTY,
- VALIDATE_3D_ERR_MISSING_IMAGES,
- VALIDATE_3D_ERR_EXTRA_IMAGES,
- VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH,
- VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH,
- VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS,
- };
-
static Image3DValidateError validate_3d_image(Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images);
static String get_3d_image_validation_error_text(Image3DValidateError p_error);
- /**
- * Resize the image, using the preferred interpolation method.
- */
+ // Resize the image, using the preferred interpolation method.
void resize_to_po2(bool p_square = false, Interpolation p_interpolation = INTERPOLATE_BILINEAR);
void resize(int p_width, int p_height, Interpolation p_interpolation = INTERPOLATE_BILINEAR);
void shrink_x2();
bool is_size_po2() const;
- /**
- * Crop the image to a specific size, if larger, then the image is filled by black
- */
+
+ // Crop the image to a specific size, if larger, then the image is filled by black.
void crop_from_point(int p_x, int p_y, int p_width, int p_height);
void crop(int p_width, int p_height);
@@ -280,34 +311,20 @@ public:
void flip_x();
void flip_y();
- /**
- * Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1)
- */
+ // Generate a mipmap chain of an image (creates an image 1/4 the size, with averaging of 4->1).
Error generate_mipmaps(bool p_renormalize = false);
- enum RoughnessChannel {
- ROUGHNESS_CHANNEL_R,
- ROUGHNESS_CHANNEL_G,
- ROUGHNESS_CHANNEL_B,
- ROUGHNESS_CHANNEL_A,
- ROUGHNESS_CHANNEL_L,
- };
-
Error generate_mipmap_roughness(RoughnessChannel p_roughness_channel, const Ref<Image> &p_normal_map);
void clear_mipmaps();
- void normalize(); //for normal maps
+ void normalize();
- /**
- * Creates new internal image data of a given size and format. Current image will be lost.
- */
+ // Creates new internal image data of a given size and format. Current image will be lost.
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
void initialize_data(const char **p_xpm);
- /**
- * returns true when the image is empty (0,0) in size
- */
+ // Returns true when the image is empty (0,0) in size.
bool is_empty() const;
Vector<uint8_t> get_data() const;
@@ -327,27 +344,14 @@ public:
static Ref<Image> create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
void set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
- /**
- * create an empty image
- */
- Image() {}
- /**
- * create an empty image of a specific size and format
- */
- Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
- /**
- * import an image of a specific size and format from a pointer
- */
- Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
+ Image() = default; // Create an empty image.
+ Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format); // Create an empty image of a specific size and format.
+ Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data); // Import an image of a specific size and format from a byte vector.
+ Image(const uint8_t *p_mem_png_jpg, int p_len = -1); // Import either a png or jpg from a pointer.
+ Image(const char **p_xpm); // Import an XPM image.
~Image() {}
- enum AlphaMode {
- ALPHA_NONE,
- ALPHA_BIT,
- ALPHA_BLEND
- };
-
AlphaMode detect_alpha() const;
bool is_invisible() const;
@@ -362,21 +366,6 @@ public:
static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap);
static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h);
- enum CompressMode {
- COMPRESS_S3TC,
- COMPRESS_ETC,
- COMPRESS_ETC2,
- COMPRESS_BPTC,
- COMPRESS_ASTC,
- COMPRESS_MAX,
- };
- enum CompressSource {
- COMPRESS_SOURCE_GENERIC,
- COMPRESS_SOURCE_SRGB,
- COMPRESS_SOURCE_NORMAL,
- COMPRESS_SOURCE_MAX,
- };
-
Error compress(CompressMode p_mode, CompressSource p_source = COMPRESS_SOURCE_GENERIC, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error decompress();
@@ -422,9 +411,6 @@ public:
void convert_ra_rgba8_to_rg();
void convert_rgba8_to_bgra8();
- Image(const uint8_t *p_mem_png_jpg, int p_len = -1);
- Image(const char **p_xpm);
-
virtual Ref<Resource> duplicate(bool p_subresources = false) const override;
UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC) const;
@@ -443,14 +429,7 @@ public:
void set_as_black();
- void copy_internals_from(const Ref<Image> &p_image) {
- ERR_FAIL_COND_MSG(p_image.is_null(), "Cannot copy image internals: invalid Image object.");
- format = p_image->format;
- width = p_image->width;
- height = p_image->height;
- mipmaps = p_image->mipmaps;
- data = p_image->data;
- }
+ void copy_internals_from(const Ref<Image> &p_image);
Dictionary compute_image_metrics(const Ref<Image> p_compared_image, bool p_luma_metric = true);
};
diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp
index 92c690dc2a..50c4704aa3 100644
--- a/core/io/image_loader.cpp
+++ b/core/io/image_loader.cpp
@@ -87,7 +87,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
if (f.is_null()) {
Error err;
f = FileAccess::open(p_file, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), err, vformat("Error opening file '%s'.", p_file));
}
String extension = p_file.get_extension();
@@ -98,7 +98,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
}
Error err = loader.write[i]->load_image(p_image, f, p_flags, p_scale);
if (err != OK) {
- ERR_PRINT("Error loading image: " + p_file);
+ ERR_PRINT(vformat("Error loading image: '%s'.", p_file));
}
if (err != ERR_FILE_UNRECOGNIZED) {
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index 38c71b19fa..3c67a8f894 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -51,7 +51,7 @@ struct _IP_ResolverPrivate {
response.clear();
type = IP::TYPE_NONE;
hostname = "";
- };
+ }
QueueItem() {
clear();
@@ -201,7 +201,7 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return IPAddress();
}
@@ -220,7 +220,7 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return Array();
}
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 22219fca29..e73677be9c 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -1402,7 +1402,7 @@ Error ResourceFormatSaverJSON::save(const Ref<Resource> &p_resource, const Strin
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err, err, "Cannot save json '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Cannot save json '%s'.", p_path));
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index 67469de5cc..d9664e7370 100644
--- a/core/io/marshalls.cpp
+++ b/core/io/marshalls.cpp
@@ -33,8 +33,6 @@
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
-#include "core/os/keyboard.h"
-#include "core/string/print_string.h"
#include <limits.h>
#include <stdio.h>
@@ -69,10 +67,31 @@ ObjectID EncodedObjectAsID::get_object_id() const {
// For `Variant::ARRAY`.
// Occupies bits 16 and 17.
#define HEADER_DATA_FIELD_TYPED_ARRAY_MASK (0b11 << 16)
-#define HEADER_DATA_FIELD_TYPED_ARRAY_NONE (0b00 << 16)
-#define HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN (0b01 << 16)
-#define HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME (0b10 << 16)
-#define HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT (0b11 << 16)
+#define HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT 16
+
+// For `Variant::DICTIONARY`.
+// Occupies bits 16 and 17.
+#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_MASK (0b11 << 16)
+#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT 16
+// Occupies bits 18 and 19.
+#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_MASK (0b11 << 18)
+#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT 18
+
+enum ContainerTypeKind {
+ CONTAINER_TYPE_KIND_NONE = 0b00,
+ CONTAINER_TYPE_KIND_BUILTIN = 0b01,
+ CONTAINER_TYPE_KIND_CLASS_NAME = 0b10,
+ CONTAINER_TYPE_KIND_SCRIPT = 0b11,
+};
+
+struct ContainerType {
+ Variant::Type builtin_type = Variant::NIL;
+ StringName class_name;
+ Ref<Script> script;
+};
+
+#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \
+ ((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT))
static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r_string) {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
@@ -80,7 +99,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
int32_t strlen = decode_uint32(buf);
int32_t pad = 0;
- // Handle padding
+ // Handle padding.
if (strlen % 4) {
pad = 4 - strlen % 4;
}
@@ -88,7 +107,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
buf += 4;
len -= 4;
- // Ensure buffer is big enough
+ // Ensure buffer is big enough.
ERR_FAIL_ADD_OF(strlen, pad, ERR_FILE_EOF);
ERR_FAIL_COND_V(strlen < 0 || strlen + pad > len, ERR_FILE_EOF);
@@ -96,10 +115,10 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
ERR_FAIL_COND_V(str.parse_utf8((const char *)buf, strlen) != OK, ERR_INVALID_DATA);
r_string = str;
- // Add padding
+ // Add padding.
strlen += pad;
- // Update buffer pos, left data count, and return size
+ // Update buffer pos, left data count, and return size.
buf += strlen;
len -= strlen;
if (r_len) {
@@ -109,6 +128,65 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
return OK;
}
+static Error _decode_container_type(const uint8_t *&buf, int &len, int *r_len, bool p_allow_objects, ContainerTypeKind p_type_kind, ContainerType &r_type) {
+ switch (p_type_kind) {
+ case CONTAINER_TYPE_KIND_NONE: {
+ return OK;
+ } break;
+ case CONTAINER_TYPE_KIND_BUILTIN: {
+ ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
+
+ int32_t bt = decode_uint32(buf);
+ buf += 4;
+ len -= 4;
+ if (r_len) {
+ (*r_len) += 4;
+ }
+
+ ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
+ r_type.builtin_type = (Variant::Type)bt;
+ if (!p_allow_objects && r_type.builtin_type == Variant::OBJECT) {
+ r_type.class_name = EncodedObjectAsID::get_class_static();
+ }
+ return OK;
+ } break;
+ case CONTAINER_TYPE_KIND_CLASS_NAME: {
+ String str;
+ Error err = _decode_string(buf, len, r_len, str);
+ if (err) {
+ return err;
+ }
+
+ r_type.builtin_type = Variant::OBJECT;
+ if (p_allow_objects) {
+ r_type.class_name = str;
+ } else {
+ r_type.class_name = EncodedObjectAsID::get_class_static();
+ }
+ return OK;
+ } break;
+ case CONTAINER_TYPE_KIND_SCRIPT: {
+ String path;
+ Error err = _decode_string(buf, len, r_len, path);
+ if (err) {
+ return err;
+ }
+
+ r_type.builtin_type = Variant::OBJECT;
+ if (p_allow_objects) {
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
+ r_type.script = ResourceLoader::load(path, "Script");
+ ERR_FAIL_COND_V_MSG(r_type.script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
+ r_type.class_name = r_type.script->get_instance_base_type();
+ } else {
+ r_type.class_name = EncodedObjectAsID::get_class_static();
+ }
+ return OK;
+ } break;
+ }
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid container type kind."); // Future proofing.
+}
+
Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_objects, int p_depth) {
ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Variant is too deep. Bailing.");
const uint8_t *buf = p_buffer;
@@ -126,7 +204,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
*r_len = 4;
}
- // Note: We cannot use sizeof(real_t) for decoding, in case a different size is encoded.
+ // NOTE: We cannot use `sizeof(real_t)` for decoding, in case a different size is encoded.
// Decoding math types always checks for the encoded size, while encoding always uses compilation setting.
// This does lead to some code duplication for decoding, but compatibility is the priority.
switch (header & HEADER_TYPE_MASK) {
@@ -188,7 +266,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
- // math types
+ // Math types.
case Variant::VECTOR2: {
Vector2 val;
if (header & HEADER_DATA_FLAG_64) {
@@ -539,7 +617,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = val;
} break;
- // misc types
+
+ // Misc types.
case Variant::COLOR: {
ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA);
Color val;
@@ -568,7 +647,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
int32_t strlen = decode_uint32(buf);
if (strlen & 0x80000000) {
- //new format
+ // New format.
ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
Vector<StringName> names;
Vector<StringName> subnames;
@@ -607,8 +686,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = NodePath(names, subnames, np_flags & 1);
} else {
- //old format, just a string
-
+ // Old format, just a string.
ERR_FAIL_V(ERR_INVALID_DATA);
}
@@ -698,9 +776,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
if (str == "script" && value.get_type() != Variant::NIL) {
ERR_FAIL_COND_V_MSG(value.get_type() != Variant::STRING, ERR_INVALID_DATA, "Invalid value for \"script\" property, expected script path as String.");
String path = value;
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
Ref<Script> script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
obj->set_script(script);
} else {
obj->set(str, value);
@@ -731,9 +809,30 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = Signal(id, StringName(name));
} break;
case Variant::DICTIONARY: {
+ ContainerType key_type;
+
+ {
+ ContainerTypeKind key_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_KEY);
+ Error err = _decode_container_type(buf, len, r_len, p_allow_objects, key_type_kind, key_type);
+ if (err) {
+ return err;
+ }
+ }
+
+ ContainerType value_type;
+
+ {
+ ContainerTypeKind value_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_VALUE);
+ Error err = _decode_container_type(buf, len, r_len, p_allow_objects, value_type_kind, value_type);
+ if (err) {
+ return err;
+ }
+ }
+
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
+
int32_t count = decode_uint32(buf);
- // bool shared = count&0x80000000;
+ //bool shared = count & 0x80000000;
count &= 0x7FFFFFFF;
buf += 4;
@@ -743,7 +842,10 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
- Dictionary d;
+ Dictionary dict;
+ if (key_type.builtin_type != Variant::NIL || value_type.builtin_type != Variant::NIL) {
+ dict.set_typed(key_type.builtin_type, key_type.class_name, key_type.script, value_type.builtin_type, value_type.class_name, value_type.script);
+ }
for (int i = 0; i < count; i++) {
Variant key, value;
@@ -767,75 +869,27 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += used;
}
- d[key] = value;
+ dict[key] = value;
}
- r_variant = d;
+ r_variant = dict;
} break;
case Variant::ARRAY: {
- Variant::Type builtin_type = Variant::VARIANT_MAX;
- StringName class_name;
- Ref<Script> script;
-
- switch (header & HEADER_DATA_FIELD_TYPED_ARRAY_MASK) {
- case HEADER_DATA_FIELD_TYPED_ARRAY_NONE:
- break; // Untyped array.
- case HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN: {
- ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
-
- int32_t bt = decode_uint32(buf);
- buf += 4;
- len -= 4;
- if (r_len) {
- (*r_len) += 4;
- }
+ ContainerType type;
- ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
- builtin_type = (Variant::Type)bt;
- if (!p_allow_objects && builtin_type == Variant::OBJECT) {
- class_name = EncodedObjectAsID::get_class_static();
- }
- } break;
- case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: {
- String str;
- Error err = _decode_string(buf, len, r_len, str);
- if (err) {
- return err;
- }
-
- builtin_type = Variant::OBJECT;
- if (p_allow_objects) {
- class_name = str;
- } else {
- class_name = EncodedObjectAsID::get_class_static();
- }
- } break;
- case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: {
- String path;
- Error err = _decode_string(buf, len, r_len, path);
- if (err) {
- return err;
- }
-
- builtin_type = Variant::OBJECT;
- if (p_allow_objects) {
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
- script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
- class_name = script->get_instance_base_type();
- } else {
- class_name = EncodedObjectAsID::get_class_static();
- }
- } break;
- default:
- ERR_FAIL_V(ERR_INVALID_DATA); // Future proofing.
+ {
+ ContainerTypeKind type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_ARRAY);
+ Error err = _decode_container_type(buf, len, r_len, p_allow_objects, type_kind, type);
+ if (err) {
+ return err;
+ }
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
- // bool shared = count&0x80000000;
+ //bool shared = count & 0x80000000;
count &= 0x7FFFFFFF;
buf += 4;
@@ -845,29 +899,29 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
- Array varr;
- if (builtin_type != Variant::VARIANT_MAX) {
- varr.set_typed(builtin_type, class_name, script);
+ Array array;
+ if (type.builtin_type != Variant::NIL) {
+ array.set_typed(type.builtin_type, type.class_name, type.script);
}
for (int i = 0; i < count; i++) {
int used = 0;
- Variant v;
- Error err = decode_variant(v, buf, len, &used, p_allow_objects, p_depth + 1);
+ Variant elem;
+ Error err = decode_variant(elem, buf, len, &used, p_allow_objects, p_depth + 1);
ERR_FAIL_COND_V_MSG(err != OK, err, "Error when trying to decode Variant.");
buf += used;
len -= used;
- varr.push_back(v);
+ array.push_back(elem);
if (r_len) {
(*r_len) += used;
}
}
- r_variant = varr;
+ r_variant = array;
} break;
- // arrays
+ // Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
@@ -906,7 +960,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<int32_t> data;
if (count) {
- //const int*rbuf=(const int*)buf;
+ //const int *rbuf = (const int *)buf;
data.resize(count);
int32_t *w = data.ptrw();
for (int32_t i = 0; i < count; i++) {
@@ -930,7 +984,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<int64_t> data;
if (count) {
- //const int*rbuf=(const int*)buf;
+ //const int *rbuf = (const int *)buf;
data.resize(count);
int64_t *w = data.ptrw();
for (int64_t i = 0; i < count; i++) {
@@ -954,7 +1008,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<float> data;
if (count) {
- //const float*rbuf=(const float*)buf;
+ //const float *rbuf = (const float *)buf;
data.resize(count);
float *w = data.ptrw();
for (int32_t i = 0; i < count; i++) {
@@ -1265,13 +1319,50 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) {
r_len += 4 + utf8.length();
while (r_len % 4) {
- r_len++; //pad
+ r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
}
}
+static void _encode_container_type_header(const ContainerType &p_type, uint32_t &header, uint32_t p_shift, bool p_full_objects) {
+ if (p_type.builtin_type != Variant::NIL) {
+ if (p_type.script.is_valid()) {
+ header |= (p_full_objects ? CONTAINER_TYPE_KIND_SCRIPT : CONTAINER_TYPE_KIND_CLASS_NAME) << p_shift;
+ } else if (p_type.class_name != StringName()) {
+ header |= CONTAINER_TYPE_KIND_CLASS_NAME << p_shift;
+ } else {
+ // No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
+ header |= CONTAINER_TYPE_KIND_BUILTIN << p_shift;
+ }
+ }
+}
+
+static Error _encode_container_type(const ContainerType &p_type, uint8_t *&buf, int &r_len, bool p_full_objects) {
+ if (p_type.builtin_type != Variant::NIL) {
+ if (p_type.script.is_valid()) {
+ if (p_full_objects) {
+ String path = p_type.script->get_path();
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for a container type.");
+ _encode_string(path, buf, r_len);
+ } else {
+ _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
+ }
+ } else if (p_type.class_name != StringName()) {
+ _encode_string(p_full_objects ? p_type.class_name.operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
+ } else {
+ // No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
+ if (buf) {
+ encode_uint32(p_type.builtin_type, buf);
+ buf += 4;
+ }
+ r_len += 4;
+ }
+ }
+ return OK;
+}
+
Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, int p_depth) {
ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential infinite recursion detected. Bailing.");
uint8_t *buf = r_buffer;
@@ -1310,20 +1401,32 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
header |= HEADER_DATA_FLAG_OBJECT_AS_ID;
}
} break;
+ case Variant::DICTIONARY: {
+ Dictionary dict = p_variant;
+
+ ContainerType key_type;
+ key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
+ key_type.class_name = dict.get_typed_key_class_name();
+ key_type.script = dict.get_typed_key_script();
+
+ _encode_container_type_header(key_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
+
+ ContainerType value_type;
+ value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
+ value_type.class_name = dict.get_typed_value_class_name();
+ value_type.script = dict.get_typed_value_script();
+
+ _encode_container_type_header(value_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
+ } break;
case Variant::ARRAY: {
Array array = p_variant;
- if (array.is_typed()) {
- Ref<Script> script = array.get_typed_script();
- if (script.is_valid()) {
- header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
- } else if (array.get_typed_class_name() != StringName()) {
- header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
- } else {
- // No need to check `p_full_objects` since for `Variant::OBJECT`
- // `array.get_typed_class_name()` should be non-empty.
- header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN;
- }
- }
+
+ ContainerType type;
+ type.builtin_type = (Variant::Type)array.get_typed_builtin();
+ type.class_name = array.get_typed_class_name();
+ type.script = array.get_typed_script();
+
+ _encode_container_type_header(type, header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
} break;
#ifdef REAL_T_IS_DOUBLE
case Variant::VECTOR2:
@@ -1344,7 +1447,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
#endif // REAL_T_IS_DOUBLE
default: {
- } // nothing to do at this stage
+ // Nothing to do at this stage.
+ } break;
}
if (buf) {
@@ -1355,7 +1459,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
switch (p_variant.get_type()) {
case Variant::NIL: {
- //nothing to do
+ // Nothing to do.
} break;
case Variant::BOOL: {
if (buf) {
@@ -1367,7 +1471,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::INT: {
if (header & HEADER_DATA_FLAG_64) {
- //64 bits
+ // 64 bits.
if (buf) {
encode_uint64(p_variant.operator int64_t(), buf);
}
@@ -1401,7 +1505,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::NODE_PATH: {
NodePath np = p_variant;
if (buf) {
- encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); //for compatibility with the old format
+ encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); // For compatibility with the old format.
encode_uint32(np.get_subname_count(), buf + 4);
uint32_t np_flags = 0;
if (np.is_absolute()) {
@@ -1451,7 +1555,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
- // math types
+ // Math types.
case Variant::VECTOR2: {
if (buf) {
Vector2 v2 = p_variant;
@@ -1635,7 +1739,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
- // misc types
+ // Misc types.
case Variant::COLOR: {
if (buf) {
Color c = p_variant;
@@ -1746,29 +1850,53 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 8;
} break;
case Variant::DICTIONARY: {
- Dictionary d = p_variant;
+ Dictionary dict = p_variant;
+
+ {
+ ContainerType key_type;
+ key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
+ key_type.class_name = dict.get_typed_key_class_name();
+ key_type.script = dict.get_typed_key_script();
+
+ Error err = _encode_container_type(key_type, buf, r_len, p_full_objects);
+ if (err) {
+ return err;
+ }
+ }
+
+ {
+ ContainerType value_type;
+ value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
+ value_type.class_name = dict.get_typed_value_class_name();
+ value_type.script = dict.get_typed_value_script();
+
+ Error err = _encode_container_type(value_type, buf, r_len, p_full_objects);
+ if (err) {
+ return err;
+ }
+ }
if (buf) {
- encode_uint32(uint32_t(d.size()), buf);
+ encode_uint32(uint32_t(dict.size()), buf);
buf += 4;
}
r_len += 4;
List<Variant> keys;
- d.get_key_list(&keys);
+ dict.get_key_list(&keys);
- for (const Variant &E : keys) {
+ for (const Variant &key : keys) {
int len;
- Error err = encode_variant(E, buf, len, p_full_objects, p_depth + 1);
+ Error err = encode_variant(key, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
r_len += len;
if (buf) {
buf += len;
}
- Variant *v = d.getptr(E);
- ERR_FAIL_NULL_V(v, ERR_BUG);
- err = encode_variant(*v, buf, len, p_full_objects, p_depth + 1);
+ Variant *value = dict.getptr(key);
+ ERR_FAIL_NULL_V(value, ERR_BUG);
+ err = encode_variant(*value, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
r_len += len;
@@ -1781,27 +1909,15 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::ARRAY: {
Array array = p_variant;
- if (array.is_typed()) {
- Variant variant = array.get_typed_script();
- Ref<Script> script = variant;
- if (script.is_valid()) {
- if (p_full_objects) {
- String path = script->get_path();
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type.");
- _encode_string(path, buf, r_len);
- } else {
- _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
- }
- } else if (array.get_typed_class_name() != StringName()) {
- _encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
- } else {
- // No need to check `p_full_objects` since for `Variant::OBJECT`
- // `array.get_typed_class_name()` should be non-empty.
- if (buf) {
- encode_uint32(array.get_typed_builtin(), buf);
- buf += 4;
- }
- r_len += 4;
+ {
+ ContainerType type;
+ type.builtin_type = (Variant::Type)array.get_typed_builtin();
+ type.class_name = array.get_typed_class_name();
+ type.script = array.get_typed_script();
+
+ Error err = _encode_container_type(type, buf, r_len, p_full_objects);
+ if (err) {
+ return err;
}
}
@@ -1811,9 +1927,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
r_len += 4;
- for (const Variant &var : array) {
+ for (const Variant &elem : array) {
int len;
- Error err = encode_variant(var, buf, len, p_full_objects, p_depth + 1);
+ Error err = encode_variant(elem, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
if (buf) {
@@ -1823,7 +1939,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
} break;
- // arrays
+
+ // Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
Vector<uint8_t> data = p_variant;
int datalen = data.size();
@@ -1939,7 +2056,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 4 + utf8.length() + 1;
while (r_len % 4) {
- r_len++; //pad
+ r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
@@ -2057,9 +2174,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
Vector<float> vector3_to_float32_array(const Vector3 *vecs, size_t count) {
- // We always allocate a new array, and we don't memcpy.
- // We also don't consider returning a pointer to the passed vectors when sizeof(real_t) == 4.
- // One reason is that we could decide to put a 4th component in Vector3 for SIMD/mobile performance,
+ // We always allocate a new array, and we don't `memcpy()`.
+ // We also don't consider returning a pointer to the passed vectors when `sizeof(real_t) == 4`.
+ // One reason is that we could decide to put a 4th component in `Vector3` for SIMD/mobile performance,
// which would cause trouble with these optimizations.
Vector<float> floats;
if (count == 0) {
diff --git a/core/io/missing_resource.cpp b/core/io/missing_resource.cpp
index c78195bc46..1c15cc7dd3 100644
--- a/core/io/missing_resource.cpp
+++ b/core/io/missing_resource.cpp
@@ -74,6 +74,10 @@ bool MissingResource::is_recording_properties() const {
return recording_properties;
}
+String MissingResource::get_save_class() const {
+ return original_class;
+}
+
void MissingResource::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingResource::set_original_class);
ClassDB::bind_method(D_METHOD("get_original_class"), &MissingResource::get_original_class);
diff --git a/core/io/missing_resource.h b/core/io/missing_resource.h
index f32d818ccb..4cded5ca24 100644
--- a/core/io/missing_resource.h
+++ b/core/io/missing_resource.h
@@ -57,6 +57,8 @@ public:
void set_recording_properties(bool p_enable);
bool is_recording_properties() const;
+ virtual String get_save_class() const override;
+
MissingResource();
};
diff --git a/core/io/net_socket.h b/core/io/net_socket.h
index 120ad5e85b..c12bab622a 100644
--- a/core/io/net_socket.h
+++ b/core/io/net_socket.h
@@ -76,6 +76,8 @@ public:
virtual void set_reuse_address_enabled(bool p_enabled) = 0;
virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
+
+ virtual ~NetSocket() {}
};
#endif // NET_SOCKET_H
diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp
index fae3de2a98..08e5353174 100644
--- a/core/io/packet_peer_udp.cpp
+++ b/core/io/packet_peer_udp.cpp
@@ -105,6 +105,19 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
return ERR_UNAVAILABLE;
}
+/* Bogus GCC warning here:
+ * In member function 'int RingBuffer<T>::read(T*, int, bool) [with T = unsigned char]',
+ * inlined from 'virtual Error PacketPeerUDP::get_packet(const uint8_t**, int&)' at core/io/packet_peer_udp.cpp:112:9,
+ * inlined from 'virtual Error PacketPeerUDP::get_packet(const uint8_t**, int&)' at core/io/packet_peer_udp.cpp:99:7:
+ * Error: ./core/ring_buffer.h:68:46: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=]
+ * 68 | p_buf[dst++] = read[pos + i];
+ * | ~~~~~~~~~~~~~^~~~~~~
+ */
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wstringop-overflow=0"
+#endif
+
uint32_t size = 0;
uint8_t ipv6[16] = {};
rb.read(ipv6, 16, true);
@@ -115,6 +128,11 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
--queue_count;
*r_buffer = packet_buffer;
r_buffer_size = size;
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
return OK;
}
diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp
index 93179d9a11..c832ef5700 100644
--- a/core/io/pck_packer.cpp
+++ b/core/io/pck_packer.cpp
@@ -48,7 +48,8 @@ static int _get_pad(int p_alignment, int p_n) {
void PCKPacker::_bind_methods() {
ClassDB::bind_method(D_METHOD("pck_start", "pck_path", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_file", "target_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_file_removal", "target_path"), &PCKPacker::add_file_removal);
ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false));
}
@@ -84,7 +85,7 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri
enc_dir = p_encrypt_directory;
file = FileAccess::open(p_pck_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_pck_path) + ".");
+ ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, vformat("Can't open file to write: '%s'.", String(p_pck_path)));
alignment = p_alignment;
@@ -106,23 +107,42 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri
return OK;
}
-Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_encrypt) {
+Error PCKPacker::add_file_removal(const String &p_target_path) {
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
- Ref<FileAccess> f = FileAccess::open(p_src, FileAccess::READ);
+ File pf;
+ // Simplify path here and on every 'files' access so that paths that have extra '/'
+ // symbols or 'res://' in them still match the MD5 hash for the saved path.
+ pf.path = p_target_path.simplify_path().trim_prefix("res://");
+ pf.ofs = ofs;
+ pf.size = 0;
+ pf.removal = true;
+
+ pf.md5.resize(16);
+ pf.md5.fill(0);
+
+ files.push_back(pf);
+
+ return OK;
+}
+
+Error PCKPacker::add_file(const String &p_target_path, const String &p_source_path, bool p_encrypt) {
+ ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
+
+ Ref<FileAccess> f = FileAccess::open(p_source_path, FileAccess::READ);
if (f.is_null()) {
return ERR_FILE_CANT_OPEN;
}
File pf;
// Simplify path here and on every 'files' access so that paths that have extra '/'
- // symbols in them still match to the MD5 hash for the saved path.
- pf.path = p_pck_path.simplify_path();
- pf.src_path = p_src;
+ // symbols or 'res://' in them still match the MD5 hash for the saved path.
+ pf.path = p_target_path.simplify_path().trim_prefix("res://");
+ pf.src_path = p_source_path;
pf.ofs = ofs;
pf.size = f->get_length();
- Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_src);
+ Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_source_path);
{
unsigned char hash[16];
CryptoCore::md5(data.ptr(), data.size(), hash);
@@ -195,6 +215,9 @@ Error PCKPacker::flush(bool p_verbose) {
if (files[i].encrypted) {
flags |= PACK_FILE_ENCRYPTED;
}
+ if (files[i].removal) {
+ flags |= PACK_FILE_REMOVAL;
+ }
fhead->store_32(flags);
}
@@ -218,6 +241,10 @@ Error PCKPacker::flush(bool p_verbose) {
int count = 0;
for (int i = 0; i < files.size(); i++) {
+ if (files[i].removal) {
+ continue;
+ }
+
Ref<FileAccess> src = FileAccess::open(files[i].src_path, FileAccess::READ);
uint64_t to_write = files[i].size;
diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h
index 5aac833532..043a1dbdb8 100644
--- a/core/io/pck_packer.h
+++ b/core/io/pck_packer.h
@@ -53,13 +53,15 @@ class PCKPacker : public RefCounted {
uint64_t ofs = 0;
uint64_t size = 0;
bool encrypted = false;
+ bool removal = false;
Vector<uint8_t> md5;
};
Vector<File> files;
public:
Error pck_start(const String &p_pck_path, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false);
- Error add_file(const String &p_pck_path, const String &p_src, bool p_encrypt = false);
+ Error add_file(const String &p_target_path, const String &p_source_path, bool p_encrypt = false);
+ Error add_file_removal(const String &p_target_path);
Error flush(bool p_verbose = false);
PCKPacker() {}
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
index 8d91e6dec2..32e83c31f2 100644
--- a/core/io/plist.cpp
+++ b/core/io/plist.cpp
@@ -450,7 +450,7 @@ PList::PList() {
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);
+ ERR_FAIL_COND_MSG(!ok, vformat("PList: %s.", err_str));
}
uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
diff --git a/core/io/remote_filesystem_client.cpp b/core/io/remote_filesystem_client.cpp
index 1198810441..c3f9a0016c 100644
--- a/core/io/remote_filesystem_client.cpp
+++ b/core/io/remote_filesystem_client.cpp
@@ -96,7 +96,7 @@ Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVecto
}
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open file for writing to remote filesystem cache: '%s'.", p_path));
f->store_buffer(p_file.ptr(), p_file.size());
Error err = f->get_error();
if (err) {
@@ -115,10 +115,10 @@ Error RemoteFilesystemClient::_store_cache_file(const Vector<FileCache> &p_cache
String full_path = cache_path.path_join(FILES_CACHE_FILE);
String base_file_dir = full_path.get_base_dir();
Error err = DirAccess::make_dir_recursive_absolute(base_file_dir);
- ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir);
+ ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, vformat("Unable to create base directory to store cache file: '%s'.", base_file_dir));
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open the remote cache file for writing: '%s'.", full_path));
f->store_line(itos(FILESYSTEM_CACHE_VERSION));
for (int i = 0; i < p_cache.size(); i++) {
String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time);
@@ -151,10 +151,10 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
tcp_client.instantiate();
IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host);
- ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host);
+ ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, vformat("Unable to resolve remote filesystem server hostname: '%s'.", p_host));
print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port));
Error err = tcp_client->connect_to_host(ip, p_port);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Unable to open connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
tcp_client->poll();
@@ -162,7 +162,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_V_MSG(ERR_CANT_CONNECT, vformat("Connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
}
// Connection OK, now send the current file state.
@@ -280,7 +280,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
err = tcp_client->get_data(file_buffer.ptr(), file_size);
if (err != OK) {
- ERR_PRINT("Error retrieving file from remote filesystem: " + file);
+ ERR_PRINT(vformat("Error retrieving file from remote filesystem: '%s'.", file));
server_disconnected = true;
}
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 0ff4fbe490..c65484b6c6 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -76,7 +76,7 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
existing->path_cache = String();
ResourceCache::resources.erase(p_path);
} else {
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ ERR_FAIL_MSG(vformat("Another resource is loaded from path '%s' (possible cyclic resource inclusion).", p_path));
}
}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index ecbb9c0104..ed11f96d03 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -411,7 +411,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
//always use internal cache for loading internal resources
if (!internal_index_cache.has(path)) {
- WARN_PRINT(String("Couldn't load resource (no cache): " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource (no cache): %s.", path));
r_v = Variant();
} else {
r_v = internal_index_cache[path];
@@ -435,7 +435,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
Ref<Resource> res = ResourceLoader::load(path, exttype, cache_mode_for_external);
if (res.is_null()) {
- WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource: %s.", path));
}
r_v = res;
@@ -458,7 +458,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", external_resources[erindex].path));
}
}
} else {
@@ -704,7 +704,7 @@ Error ResourceLoaderBinary::load() {
ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", path));
}
}
}
@@ -780,7 +780,7 @@ Error ResourceLoaderBinary::load() {
obj = missing_resource;
} else {
error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource of unrecognized type in file: '%s'.", local_path, t));
}
}
@@ -789,7 +789,7 @@ Error ResourceLoaderBinary::load() {
String obj_class = obj->get_class();
error = ERR_FILE_CORRUPT;
memdelete(obj); //bye
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource type in resource field not a resource, type is: %s.", local_path, obj_class));
}
res = Ref<Resource>(r);
@@ -833,7 +833,7 @@ Error ResourceLoaderBinary::load() {
}
bool set_valid = true;
- if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) {
+ if (value.get_type() == Variant::OBJECT && missing_resource == nullptr && ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
// If the property being set is a missing resource (and the parent is not),
// then setting it will most likely not work.
// Instead, save it as metadata.
@@ -999,7 +999,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
error = fac->open_after_magic(f);
if (error != OK) {
f.unref();
- ERR_FAIL_MSG("Failed to open binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Failed to open binary resource file: '%s'.", local_path));
}
f = fac;
@@ -1007,7 +1007,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
// Not normal.
error = ERR_FILE_UNRECOGNIZED;
f.unref();
- ERR_FAIL_MSG("Unrecognized binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Unrecognized binary resource file: '%s'.", local_path));
}
bool big_endian = f->get_32();
@@ -1093,10 +1093,10 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
#ifdef TOOLS_ENABLED
// Silence a warning that can happen during the initial filesystem scan due to cache being regenerated.
if (ResourceLoader::get_resource_uid(res_path) != er.uid) {
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
}
#else
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
#endif
}
}
@@ -1120,7 +1120,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
if (f->eof_reached()) {
error = ERR_FILE_CORRUPT;
f.unref();
- ERR_FAIL_MSG("Premature end of file (EOF): " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Premature end of file (EOF): '%s'.", local_path));
}
}
@@ -1224,7 +1224,7 @@ Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const Strin
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
switch (p_cache_mode) {
@@ -1301,7 +1301,7 @@ bool ResourceFormatLoaderBinary::handles_type(const String &p_type) const {
void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1311,7 +1311,7 @@ void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<Str
Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -1324,23 +1324,23 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.depren'.", p_path));
fw = facw;
} else if (header[0] != 'R' || header[1] != 'S' || header[2] != 'R' || header[3] != 'C') {
// Not normal.
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unrecognized binary resource file '" + local_path + "'.");
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unrecognized binary resource file '%s'.", local_path));
} else {
fw = FileAccess::open(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.depren'.", p_path));
uint8_t magic[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magic, 4);
@@ -1372,12 +1372,12 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
// Use the old approach.
- WARN_PRINT("This file is old, so it can't refactor dependencies, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it can't refactor dependencies, opening and resaving '%s'.", p_path));
Error err;
f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1523,7 +1523,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
void ResourceFormatLoaderBinary::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1577,6 +1577,10 @@ ResourceUID::ID ResourceFormatLoaderBinary::get_resource_uid(const String &p_pat
return loader.uid;
}
+bool ResourceFormatLoaderBinary::has_custom_uid_support() const {
+ return true;
+}
+
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
@@ -2027,7 +2031,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
if (!p_main && (!bundle_resources) && !res->is_built_in()) {
if (res->get_path() == path) {
- ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded.");
+ ERR_PRINT(vformat("Circular reference to resource being saved found: '%s' will be null next time it's loaded.", local_path));
return;
}
int idx = external_resources.size();
@@ -2153,7 +2157,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
f = FileAccess::open(p_path, FileAccess::WRITE, &err);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create file '%s'.", p_path));
relative_paths = p_flags & ResourceSaver::FLAG_RELATIVE_PATHS;
skip_editor = p_flags & ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
@@ -2225,10 +2229,10 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
List<ResourceData> resources;
- Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary());
-
{
for (const Ref<Resource> &E : saved_resources) {
+ Dictionary missing_resource_properties = E->get_meta(META_MISSING_RESOURCES, Dictionary());
+
ResourceData &rd = resources.push_back(ResourceData())->get();
rd.type = _resource_get_class(E);
@@ -2243,7 +2247,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
continue;
}
- if ((F.usage & PROPERTY_USAGE_STORAGE)) {
+ if ((F.usage & PROPERTY_USAGE_STORAGE) || missing_resource_properties.has(F.name)) {
Property p;
p.name_idx = get_string_index(F.name);
@@ -2258,7 +2262,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
p.value = E->get(F.name);
}
- if (p.pi.type == Variant::OBJECT && missing_resource_properties.has(F.name)) {
+ if (F.type == Variant::OBJECT && missing_resource_properties.has(F.name)) {
// Was this missing resource overridden? If so do not save the old value.
Ref<Resource> res = p.value;
if (res.is_null()) {
@@ -2384,7 +2388,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceUID::ID p_uid) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -2397,14 +2401,14 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.uidren'.", p_path));
fw = facw;
@@ -2413,7 +2417,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
return ERR_FILE_UNRECOGNIZED;
} else {
fw = FileAccess::open(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.uidren'.", p_path));
uint8_t magich[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magich, 4);
@@ -2444,7 +2448,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
// Use the old approach.
- WARN_PRINT("This file is old, so it does not support UIDs, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it does not support UIDs, opening and resaving '%s'.", p_path));
return ERR_UNAVAILABLE;
}
diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h
index 222e633e58..ec8d7ead5d 100644
--- a/core/io/resource_format_binary.h
+++ b/core/io/resource_format_binary.h
@@ -118,6 +118,7 @@ public:
virtual String get_resource_script_class(const String &p_path) const override;
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override;
+ virtual bool has_custom_uid_support() const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override;
};
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index 1ae50d2d0d..b7a14f2b88 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -75,7 +75,7 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
if (err == ERR_FILE_EOF) {
return OK;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::load - %s.import:%d error: %s.", p_path, lines, error_text));
return err;
}
@@ -335,7 +335,7 @@ void ResourceFormatImporter::get_internal_resource_path_list(const String &p_pat
if (err == ERR_FILE_EOF) {
return;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::get_internal_resource_path_list - %s.import:%d error: %s.", p_path, lines, error_text));
return;
}
@@ -387,6 +387,10 @@ ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) c
return pat.uid;
}
+bool ResourceFormatImporter::has_custom_uid_support() const {
+ return true;
+}
+
Error ResourceFormatImporter::get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const {
PathAndType pat;
Error err = _get_path_and_type(p_path, pat);
diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h
index 221f38494b..c3d3c4b67e 100644
--- a/core/io/resource_importer.h
+++ b/core/io/resource_importer.h
@@ -70,6 +70,7 @@ public:
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override;
+ virtual bool has_custom_uid_support() const override;
virtual Variant get_resource_metadata(const String &p_path) const;
virtual bool is_import_valid(const String &p_path) const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
@@ -147,8 +148,8 @@ public:
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
virtual String get_option_group_file() const { return String(); }
- virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0;
- virtual bool can_import_threaded() const { return true; }
+ virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0;
+ virtual bool can_import_threaded() const { return false; }
virtual void import_threaded_begin() {}
virtual void import_threaded_end() {}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index f026d5416c..3fea697d0b 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/core_bind.h"
+#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/resource_importer.h"
#include "core/object/script_language.h"
@@ -40,6 +41,7 @@
#include "core/os/safe_binary_mutex.h"
#include "core/string/print_string.h"
#include "core/string/translation_server.h"
+#include "core/templates/rb_set.h"
#include "core/variant/variant_parser.h"
#include "servers/rendering_server.h"
@@ -112,10 +114,21 @@ String ResourceFormatLoader::get_resource_script_class(const String &p_path) con
ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const {
int64_t uid = ResourceUID::INVALID_ID;
- GDVIRTUAL_CALL(_get_resource_uid, p_path, uid);
+ if (has_custom_uid_support()) {
+ GDVIRTUAL_CALL(_get_resource_uid, p_path, uid);
+ } else {
+ Ref<FileAccess> file = FileAccess::open(p_path + ".uid", FileAccess::READ);
+ if (file.is_valid()) {
+ uid = ResourceUID::get_singleton()->text_to_id(file->get_line());
+ }
+ }
return uid;
}
+bool ResourceFormatLoader::has_custom_uid_support() const {
+ return GDVIRTUAL_IS_OVERRIDDEN(_get_resource_uid);
+}
+
void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
if (p_type.is_empty() || handles_type(p_type)) {
get_recognized_extensions(p_extensions);
@@ -162,7 +175,7 @@ Ref<Resource> ResourceFormatLoader::load(const String &p_path, const String &p_o
}
}
- ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type.");
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed to load resource '%s'. ResourceFormatLoader::load was not implemented for this resource type.", p_path));
}
void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
@@ -282,13 +295,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
load_paths_stack.push_back(original_path);
// Try all loaders and pick the first match for the type hint
- bool found = false;
+ bool loader_found = false;
Ref<Resource> res;
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(p_path, p_type_hint)) {
continue;
}
- found = true;
+ loader_found = true;
res = loader[i]->load(p_path, original_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
if (!res.is_null()) {
break;
@@ -303,15 +316,24 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
return res;
}
- ERR_FAIL_COND_V_MSG(found, Ref<Resource>(),
- vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path));
+ if (!loader_found) {
+ if (r_error) {
+ *r_error = ERR_FILE_UNRECOGNIZED;
+ }
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint));
+ }
#ifdef TOOLS_ENABLED
Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
- ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint));
+ if (!file_check->file_exists(p_path)) {
+ if (r_error) {
+ *r_error = ERR_FILE_NOT_FOUND;
+ }
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint));
+ }
#endif
- ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path));
}
// This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame.
@@ -1150,6 +1172,21 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
return ResourceUID::INVALID_ID;
}
+bool ResourceLoader::has_custom_uid_support(const String &p_path) {
+ String local_path = _validate_local_path(p_path);
+
+ for (int i = 0; i < loader_count; i++) {
+ if (!loader[i]->recognize_path(local_path)) {
+ continue;
+ }
+ if (loader[i]->has_custom_uid_support()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_remapped) {
String new_path = p_path;
@@ -1163,7 +1200,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
String locale = TranslationServer::get_singleton()->get_locale();
- ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, vformat("Could not remap path '%s' for translation as configured locale '%s' is invalid.", p_path, locale));
Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
@@ -1222,7 +1259,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
if (err == ERR_FILE_EOF) {
break;
} else if (err != OK) {
- ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + ".");
+ ERR_PRINT(vformat("Parse error: %s.remap:%d error: %s.", p_path, lines, error_text));
break;
}
@@ -1439,6 +1476,60 @@ bool ResourceLoader::is_cleaning_tasks() {
return cleaning_tasks;
}
+Vector<String> ResourceLoader::list_directory(const String &p_directory) {
+ RBSet<String> files_found;
+ Ref<DirAccess> dir = DirAccess::open(p_directory);
+ if (dir.is_null()) {
+ return Vector<String>();
+ }
+
+ Error err = dir->list_dir_begin();
+ if (err != OK) {
+ return Vector<String>();
+ }
+
+ String d = dir->get_next();
+ while (!d.is_empty()) {
+ bool recognized = false;
+ if (dir->current_is_dir()) {
+ if (d != "." && d != "..") {
+ d += "/";
+ recognized = true;
+ }
+ } else {
+ if (d.ends_with(".import") || d.ends_with(".remap") || d.ends_with(".uid")) {
+ d = d.substr(0, d.rfind("."));
+ }
+
+ if (d.ends_with(".gdc")) {
+ d = d.substr(0, d.rfind("."));
+ d += ".gd";
+ }
+
+ const String full_path = p_directory.path_join(d);
+ // Try all loaders and pick the first match for the type hint.
+ for (int i = 0; i < loader_count; i++) {
+ if (loader[i]->recognize_path(full_path)) {
+ recognized = true;
+ break;
+ }
+ }
+ }
+
+ if (recognized) {
+ files_found.insert(d);
+ }
+ d = dir->get_next();
+ }
+
+ Vector<String> ret;
+ for (const String &f : files_found) {
+ ret.push_back(f);
+ }
+
+ return ret;
+}
+
void ResourceLoader::initialize() {}
void ResourceLoader::finalize() {}
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index caaf9f8f45..ebd6024033 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -81,6 +81,7 @@ public:
virtual String get_resource_type(const String &p_path) const;
virtual String get_resource_script_class(const String &p_path) const;
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
+ virtual bool has_custom_uid_support() const;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
virtual bool is_import_valid(const String &p_path) const { return true; }
@@ -222,7 +223,7 @@ public:
static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr);
static Ref<Resource> load_threaded_get(const String &p_path, Error *r_error = nullptr);
- static bool is_within_load() { return load_nesting > 0; };
+ static bool is_within_load() { return load_nesting > 0; }
static void resource_changed_connect(Resource *p_source, const Callable &p_callable, uint32_t p_flags);
static void resource_changed_disconnect(Resource *p_source, const Callable &p_callable);
@@ -238,6 +239,7 @@ public:
static String get_resource_type(const String &p_path);
static String get_resource_script_class(const String &p_path);
static ResourceUID::ID get_resource_uid(const String &p_path);
+ static bool has_custom_uid_support(const String &p_path);
static void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
static Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
static bool is_import_valid(const String &p_path);
@@ -302,6 +304,8 @@ public:
static bool is_cleaning_tasks();
+ static Vector<String> list_directory(const String &p_directory);
+
static void initialize();
static void finalize();
};
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index 1dc1245355..d49037dbe0 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -98,7 +98,7 @@ void ResourceFormatSaver::_bind_methods() {
}
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, vformat("Can't save empty resource to path '%s'.", p_path));
String path = p_path;
if (path.is_empty()) {
path = p_resource->get_path();
diff --git a/core/io/stream_peer.cpp b/core/io/stream_peer.cpp
index c49e15a3a0..3f1c468fb3 100644
--- a/core/io/stream_peer.cpp
+++ b/core/io/stream_peer.cpp
@@ -223,13 +223,13 @@ void StreamPeer::put_var(const Variant &p_variant, bool p_full_objects) {
}
uint8_t StreamPeer::get_u8() {
- uint8_t buf[1];
+ uint8_t buf[1] = {};
get_data(buf, 1);
return buf[0];
}
int8_t StreamPeer::get_8() {
- uint8_t buf[1];
+ uint8_t buf[1] = {};
get_data(buf, 1);
return buf[0];
}
diff --git a/core/io/tcp_server.cpp b/core/io/tcp_server.cpp
index f2b3d5e56a..d69d1f1b29 100644
--- a/core/io/tcp_server.cpp
+++ b/core/io/tcp_server.cpp
@@ -112,7 +112,7 @@ Ref<StreamPeerTCP> TCPServer::take_connection() {
return conn;
}
- conn = Ref<StreamPeerTCP>(memnew(StreamPeerTCP));
+ conn.instantiate();
conn->accept_socket(ns, ip, port);
return conn;
}
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 578cd91c52..7a11d06df6 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -31,7 +31,6 @@
#include "translation_loader_po.h"
#include "core/io/file_access.h"
-#include "core/string/translation.h"
#include "core/string/translation_po.h"
Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_error) {
@@ -170,14 +169,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
// If we reached last line and it's not a content line, break, otherwise let processing that last loop
if (is_eof && l.is_empty()) {
if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected EOF while reading PO file at: %s:%d.", path, line));
} else {
break;
}
}
if (l.begins_with("msgctxt")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: %s:%d.", path, line));
// In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read
// and set "entered_context" to true to prevent adding twice.
@@ -185,7 +184,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -197,9 +196,9 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (l.begins_with("msgid_plural")) {
if (plural_forms == 0) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: %s:%d.", path, line));
} else if (status != STATUS_READING_ID) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: %s:%d.", path, line));
}
// We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already.
// We just have to reset variables related to plurals for "msgstr[]" later on.
@@ -209,14 +208,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
msgs_plural.resize(plural_forms);
status = STATUS_READING_PLURAL;
} else if (l.begins_with("msgid")) {
- ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgid', was expecting 'msgstr' while parsing: %s:%d.", path, line));
if (!msg_id.is_empty()) {
if (!skip_this && !entered_context) {
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -245,11 +244,11 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
if (l.begins_with("msgstr[")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: %s:%d.", path, line));
plural_index++; // Increment to add to the next slot in vector msgs_plural.
l = l.substr(9, l.length()).strip_edges();
} else if (l.begins_with("msgstr")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: %s:%d.", path, line));
l = l.substr(6, l.length()).strip_edges();
status = STATUS_READING_STRING;
}
@@ -262,7 +261,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
continue; // Nothing to read or comment.
}
- ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), vformat("Invalid line '%s' while parsing: %s:%d.", l, path, line));
l = l.substr(1, l.length());
// Find final quote, ignoring escaped ones (\").
@@ -284,7 +283,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
escape_next = false;
}
- ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), vformat("Expected '\"' at end of message while parsing: %s:%d.", path, line));
l = l.substr(0, end_pos);
l = l.c_unescape();
@@ -296,7 +295,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
} else if (status == STATUS_READING_CONTEXT) {
msg_context += l;
} else if (status == STATUS_READING_PLURAL && plural_index >= 0) {
- ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), "Unexpected plural form while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), vformat("Unexpected plural form while parsing: %s:%d.", path, line));
msgs_plural.write[plural_index] = msgs_plural[plural_index] + l;
}
@@ -314,13 +313,13 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
} else if (status == STATUS_READING_PLURAL) {
if (!skip_this && !msg_id.is_empty()) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
}
- ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), "No config found in file: " + path + ".");
+ ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), vformat("No config found in file: '%s'.", path));
Vector<String> configs = config.split("\n");
for (int i = 0; i < configs.size(); i++) {
@@ -350,7 +349,7 @@ Ref<Resource> TranslationLoaderPO::load(const String &p_path, const String &p_or
}
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
return load_translation(f, r_error);
}
@@ -361,7 +360,7 @@ void TranslationLoaderPO::get_recognized_extensions(List<String> *p_extensions)
}
bool TranslationLoaderPO::handles_type(const String &p_type) const {
- return (p_type == "Translation");
+ return (p_type == "Translation") || (p_type == "TranslationPO");
}
String TranslationLoaderPO::get_resource_type(const String &p_path) const {
diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp
index 06888c7cda..6923ca1269 100644
--- a/core/io/xml_parser.cpp
+++ b/core/io/xml_parser.cpp
@@ -429,7 +429,7 @@ String XMLParser::get_named_attribute_value(const String &p_name) const {
}
}
- ERR_FAIL_COND_V_MSG(idx < 0, "", "Attribute not found: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(idx < 0, "", vformat("Attribute not found: '%s'.", p_name));
return attributes[idx].value;
}
@@ -493,7 +493,7 @@ Error XMLParser::open(const String &p_path) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
length = file->get_length();
ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT);
diff --git a/core/math/basis.h b/core/math/basis.h
index 236d666103..2d4994de19 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -223,7 +223,7 @@ struct [[nodiscard]] Basis {
static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
- Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); };
+ Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); }
Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); }
Basis(const Vector3 &p_axis, real_t p_angle) { set_axis_angle(p_axis, p_angle); }
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 0692ece1e6..35303fe9ac 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -1491,7 +1491,7 @@ Error Expression::parse(const String &p_expression, const Vector<String> &p_inpu
}
Variant Expression::execute(const Array &p_inputs, Object *p_base, bool p_show_error, bool p_const_calls_only) {
- ERR_FAIL_COND_V_MSG(error_set, Variant(), "There was previously a parse error: " + error_str + ".");
+ ERR_FAIL_COND_V_MSG(error_set, Variant(), vformat("There was previously a parse error: %s.", error_str));
execution_error = false;
Variant output;
diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp
index a49826958a..376d5d0b43 100644
--- a/core/math/geometry_2d.cpp
+++ b/core/math/geometry_2d.cpp
@@ -76,7 +76,7 @@ struct _AtlasWorkRect {
Size2i s;
Point2i p;
int idx = 0;
- _FORCE_INLINE_ bool operator<(const _AtlasWorkRect &p_r) const { return s.width > p_r.s.width; };
+ _FORCE_INLINE_ bool operator<(const _AtlasWorkRect &p_r) const { return s.width > p_r.s.width; }
};
struct _AtlasWorkRectResult {
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 83ebdc5a84..abd395d8df 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -451,17 +451,17 @@ public:
return H;
}
- static Vector<Point2i> bresenham_line(const Point2i &p_start, const Point2i &p_end) {
+ static Vector<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to) {
Vector<Point2i> points;
- Vector2i delta = (p_end - p_start).abs() * 2;
- Vector2i step = (p_end - p_start).sign();
- Vector2i current = p_start;
+ Vector2i delta = (p_to - p_from).abs() * 2;
+ Vector2i step = (p_to - p_from).sign();
+ Vector2i current = p_from;
if (delta.x > delta.y) {
int err = delta.x / 2;
- for (; current.x != p_end.x; current.x += step.x) {
+ for (; current.x != p_to.x; current.x += step.x) {
points.push_back(current);
err -= delta.y;
@@ -473,7 +473,7 @@ public:
} else {
int err = delta.y / 2;
- for (; current.y != p_end.y; current.y += step.y) {
+ for (; current.y != p_to.y; current.y += step.y) {
points.push_back(current);
err -= delta.x;
diff --git a/core/math/plane.h b/core/math/plane.h
index 6529fea60a..65783ff4cf 100644
--- a/core/math/plane.h
+++ b/core/math/plane.h
@@ -40,7 +40,7 @@ struct [[nodiscard]] Plane {
real_t d = 0;
void set_normal(const Vector3 &p_normal);
- _FORCE_INLINE_ Vector3 get_normal() const { return normal; };
+ _FORCE_INLINE_ Vector3 get_normal() const { return normal; }
void normalize();
Plane normalized() const;
diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp
index c55226a57e..7f77b0786c 100644
--- a/core/math/rect2.cpp
+++ b/core/math/rect2.cpp
@@ -283,7 +283,7 @@ next4:
}
Rect2::operator String() const {
- return "[P: " + position.operator String() + ", S: " + size + "]";
+ return "[P: " + position.operator String() + ", S: " + size.operator String() + "]";
}
Rect2::operator Rect2i() const {
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index e86b97d6a8..0590ee8a37 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -203,7 +203,7 @@ bool Vector2::is_finite() const {
}
Vector2::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ")";
}
Vector2::operator Vector2i() const {
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index 1e90002665..e18ac3b011 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -165,7 +165,7 @@ bool Vector3::is_finite() const {
}
Vector3::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ")";
}
Vector3::operator Vector3i() const {
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 9826d73a9d..d48e1a3622 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -352,7 +352,7 @@ StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class)
StringName ClassDB::_get_parent_class(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, StringName(), "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, StringName(), vformat("Cannot get class '%s'.", String(p_class)));
return ti->inherits;
}
@@ -367,7 +367,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, API_NONE, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, API_NONE, vformat("Cannot get class '%s'.", String(p_class)));
return ti->api;
}
@@ -390,7 +390,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) {
for (const StringName &E : class_list) {
ClassInfo *t = classes.getptr(E);
- ERR_FAIL_NULL_V_MSG(t, 0, "Cannot get class '" + String(E) + "'.");
+ ERR_FAIL_NULL_V_MSG(t, 0, vformat("Cannot get class '%s'.", String(E)));
if (t->api != p_api || !t->exposed) {
continue;
}
@@ -547,14 +547,14 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, vformat("Class '%s' or its base class cannot be instantiated.", String(p_class)));
}
#ifdef TOOLS_ENABLED
if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor.");
+ ERR_PRINT(vformat("Class '%s' can only be instantiated by editor.", String(p_class)));
return nullptr;
}
#endif
@@ -653,8 +653,8 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class)
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
}
// Make a "fake" extension to act as a placeholder.
@@ -734,9 +734,9 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_MSG(ti, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_MSG(ti->disabled, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_MSG(ti->gdextension, "Class '" + String(p_class) + "' has no native extension.");
+ ERR_FAIL_NULL_MSG(ti, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_MSG(ti->disabled, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_MSG(ti->gdextension, vformat("Class '%s' has no native extension.", String(p_class)));
}
p_object->_extension = ti->gdextension;
@@ -750,69 +750,87 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
}
bool ClassDB::can_instantiate(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
- }
#ifdef TOOLS_ENABLED
- if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- return false;
- }
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+ return false;
+ }
#endif
- return _can_instantiate(ti);
+ return _can_instantiate(ti);
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
}
bool ClassDB::is_abstract(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && scr->is_abstract();
- }
- if (ti->creation_func != nullptr) {
- return false;
- }
- if (!ti->gdextension) {
- return true;
- }
+ if (ti->creation_func != nullptr) {
+ return false;
+ }
+ if (!ti->gdextension) {
+ return true;
+ }
#ifndef DISABLE_DEPRECATED
- return ti->gdextension->create_instance2 == nullptr && ti->gdextension->create_instance == nullptr;
+ return ti->gdextension->create_instance2 == nullptr && ti->gdextension->create_instance == nullptr;
#else
- return ti->gdextension->create_instance2 == nullptr;
+ return ti->gdextension->create_instance2 == nullptr;
#endif // DISABLE_DEPRECATED
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && scr->is_abstract();
}
bool ClassDB::is_virtual(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && scr->is_abstract();
- }
#ifdef TOOLS_ENABLED
- if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- return false;
- }
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+ return false;
+ }
#endif
- return (_can_instantiate(ti) && ti->is_virtual);
+ return (_can_instantiate(ti) && ti->is_virtual);
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && scr->is_abstract();
}
void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherits) {
@@ -820,7 +838,7 @@ void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherit
const StringName &name = p_class;
- ERR_FAIL_COND_MSG(classes.has(name), "Class '" + String(p_class) + "' already exists.");
+ ERR_FAIL_COND_MSG(classes.has(name), vformat("Class '%s' already exists.", String(p_class)));
classes[name] = ClassInfo();
ClassInfo &ti = classes[name];
@@ -1328,7 +1346,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal)
#ifdef DEBUG_METHODS_ENABLED
ClassInfo *check = type;
while (check) {
- ERR_FAIL_COND_MSG(check->signal_map.has(sname), "Class '" + String(p_class) + "' already has signal '" + String(sname) + "'.");
+ ERR_FAIL_COND_MSG(check->signal_map.has(sname), vformat("Class '%s' already has signal '%s'.", String(p_class), String(sname)));
check = check->inherits_ptr;
}
#endif
@@ -1442,10 +1460,10 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_set = get_method(p_class, p_setter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_set, vformat("Invalid setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
int exp_args = 1 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, "Invalid function for setter '" + p_class + "::" + p_setter + " for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, vformat("Invalid function for setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
#endif
}
@@ -1454,15 +1472,15 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_get = get_method(p_class, p_getter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_get, vformat("Invalid getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
int exp_args = 0 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, "Invalid function for getter '" + p_class + "::" + p_getter + "' for property: '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, vformat("Invalid function for getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
#endif
}
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), "Object '" + p_class + "' already has property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), vformat("Object '%s' already has property '%s'.", p_class, p_pinfo.name));
#endif
OBJTYPE_WLOCK
@@ -1847,7 +1865,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
ClassInfo *type = classes.getptr(p_class);
if (!type) {
- ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'.");
+ ERR_FAIL_MSG(vformat("Couldn't bind custom method '%s' for instance '%s'.", p_method->get_name(), p_class));
}
if (p_compatibility) {
@@ -1857,7 +1875,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
if (type->method_map.has(p_method->get_name())) {
// overloading not supported
- ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'.");
+ ERR_FAIL_MSG(vformat("Method already bound '%s::%s'.", p_class, p_method->get_name()));
}
#ifdef DEBUG_METHODS_ENABLED
@@ -1888,7 +1906,7 @@ MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p
if (type->method_map.has(p_name)) {
memdelete(bind);
// Overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + ".");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound: '%s::%s'.", instance_type, p_name));
}
type->method_map[p_name] = bind;
#ifdef DEBUG_METHODS_ENABLED
@@ -1916,26 +1934,26 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + ".");
+ ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, vformat("Class '%s' already has a method '%s'.", String(instance_type), String(mdname)));
#endif
ClassInfo *type = classes.getptr(instance_type);
if (!type) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Couldn't bind method '%s' for instance '%s'.", mdname, instance_type));
}
if (!p_compatibility && type->method_map.has(mdname)) {
memdelete(p_bind);
// overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound '%s::%s'.", instance_type, mdname));
}
#ifdef DEBUG_METHODS_ENABLED
if (method_name.args.size() > p_bind->get_argument_count()) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Method definition provides more arguments than the method actually has '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method definition provides more arguments than the method actually has '%s::%s'.", instance_type, mdname));
}
p_bind->set_argument_names(method_name.args);
@@ -1964,7 +1982,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
}
void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
OBJTYPE_WLOCK;
@@ -1979,7 +1997,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (!p_object_core) {
if (p_arg_names.size() != mi.arguments.size()) {
- WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name);
+ WARN_PRINT(vformat("Mismatch argument name count for virtual method: '%s::%s'.", String(p_class), p_method.name));
} else {
List<PropertyInfo>::Iterator itr = mi.arguments.begin();
for (int i = 0; i < p_arg_names.size(); ++itr, ++i) {
@@ -1990,7 +2008,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (classes[p_class].virtual_methods_map.has(p_method.name)) {
// overloading not supported
- ERR_FAIL_MSG("Virtual method already bound '" + String(p_class) + "::" + p_method.name + "'.");
+ ERR_FAIL_MSG(vformat("Virtual method already bound '%s::%s'.", String(p_class), p_method.name));
}
classes[p_class].virtual_methods.push_back(mi);
classes[p_class].virtual_methods_map[p_method.name] = mi;
@@ -1999,7 +2017,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
}
void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
@@ -2020,7 +2038,7 @@ void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p
}
void ClassDB::add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
PackedStringArray arg_names;
@@ -2044,7 +2062,7 @@ void ClassDB::add_extension_class_virtual_method(const StringName &p_class, cons
void ClassDB::set_class_enabled(const StringName &p_class, bool p_enable) {
OBJTYPE_WLOCK;
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
classes[p_class].disabled = !p_enable;
}
@@ -2058,7 +2076,7 @@ bool ClassDB::is_class_enabled(const StringName &p_class) {
}
}
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return !ti->disabled;
}
@@ -2066,7 +2084,7 @@ bool ClassDB::is_class_exposed(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->exposed;
}
@@ -2074,7 +2092,7 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->reloadable;
}
@@ -2082,7 +2100,7 @@ bool ClassDB::is_class_runtime(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->is_runtime;
}
@@ -2192,14 +2210,14 @@ Variant ClassDB::class_get_default_property_value(const StringName &p_class, con
void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
GLOBAL_LOCK_FUNCTION;
- ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), "Class already registered: " + String(p_extension->class_name));
- ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), "Parent class name for extension class not found: " + String(p_extension->parent_class_name));
+ ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), vformat("Class already registered: '%s'.", String(p_extension->class_name)));
+ ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), vformat("Parent class name for extension class not found: '%s'.", String(p_extension->parent_class_name)));
ClassInfo *parent = classes.getptr(p_extension->parent_class_name);
#ifdef TOOLS_ENABLED
// @todo This is a limitation of the current implementation, but it should be possible to remove.
- ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class");
+ ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, vformat("Extension runtime class '%s' cannot descend from '%s' which isn't also a runtime class.", String(p_extension->class_name), parent->name));
#endif
ClassInfo c;
@@ -2215,7 +2233,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
concrete_ancestor->gdextension != nullptr) {
concrete_ancestor = concrete_ancestor->inherits_ptr;
}
- ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, "Extension class " + String(p_extension->class_name) + " cannot extend native abstract class " + String(concrete_ancestor->name));
+ ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, vformat("Extension class '%s' cannot extend native abstract class '%s'.", String(p_extension->class_name), String(concrete_ancestor->name)));
c.creation_func = concrete_ancestor->creation_func;
}
c.inherits = parent->name;
@@ -2239,7 +2257,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
void ClassDB::unregister_extension_class(const StringName &p_class, bool p_free_method_binds) {
ClassInfo *c = classes.getptr(p_class);
- ERR_FAIL_NULL_MSG(c, "Class '" + String(p_class) + "' does not exist.");
+ ERR_FAIL_NULL_MSG(c, vformat("Class '%s' does not exist.", String(p_class)));
if (p_free_method_binds) {
for (KeyValue<StringName, MethodBind *> &F : c->method_map) {
memdelete(F.value);
diff --git a/core/object/message_queue.h b/core/object/message_queue.h
index 673eb3845b..64e244bda8 100644
--- a/core/object/message_queue.h
+++ b/core/object/message_queue.h
@@ -153,7 +153,7 @@ public:
bool is_flushing() const;
int get_max_buffer_usage() const;
- CallQueue(Allocator *p_custom_allocator = 0, uint32_t p_max_pages = 8192, const String &p_error_text = String());
+ CallQueue(Allocator *p_custom_allocator = nullptr, uint32_t p_max_pages = 8192, const String &p_error_text = String());
virtual ~CallQueue();
};
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index 2f9a2d1679..e06eb1f8fa 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -109,7 +109,7 @@ public:
_FORCE_INLINE_ StringName get_instance_class() const { return instance_class; }
_FORCE_INLINE_ void set_instance_class(const StringName &p_class) { instance_class = p_class; }
- _FORCE_INLINE_ int get_argument_count() const { return argument_count; };
+ _FORCE_INLINE_ int get_argument_count() const { return argument_count; }
#ifdef TOOLS_ENABLED
virtual bool is_valid() const { return true; }
diff --git a/core/object/object.cpp b/core/object/object.cpp
index b3a4ec6e2e..ef1ca8132c 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -750,7 +750,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
Callable::CallError ce;
const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'callv': %s.", Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce)));
}
return ret;
}
@@ -999,7 +999,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (E) {
E->value = p_value;
} else {
- ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
+ ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), vformat("Invalid metadata identifier: '%s'.", p_name));
Variant *V = &metadata.insert(p_name, p_value)->value;
const String &sname = p_name;
@@ -1015,7 +1015,7 @@ Variant Object::get_meta(const StringName &p_name, const Variant &p_default) con
if (p_default != Variant()) {
return p_default;
} else {
- ERR_FAIL_V_MSG(Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'.");
+ ERR_FAIL_V_MSG(Variant(), vformat("The object does not have any 'meta' values with the key '%s'.", p_name));
}
}
return metadata[p_name];
@@ -1071,8 +1071,8 @@ void Object::get_meta_list(List<StringName> *p_list) const {
void Object::add_user_signal(const MethodInfo &p_signal) {
ERR_FAIL_COND_MSG(p_signal.name.is_empty(), "Signal name cannot be empty.");
- ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), "User signal's name conflicts with a built-in signal of '" + get_class_name() + "'.");
- ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), "Trying to add already existing signal '" + p_signal.name + "'.");
+ ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), vformat("User signal's name conflicts with a built-in signal of '%s'.", get_class_name()));
+ ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), vformat("Trying to add already existing signal '%s'.", p_signal.name));
SignalData s;
s.user = p_signal;
signal_map[p_signal.name] = s;
@@ -1137,7 +1137,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
#ifdef DEBUG_ENABLED
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_name);
//check in script
- ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, "Can't emit non-existing signal " + String("\"") + p_name + "\".");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, vformat("Can't emit non-existing signal \"%s\".", p_name));
#endif
//not connected? just return
return ERR_UNAVAILABLE;
@@ -1210,7 +1210,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) {
//most likely object is not initialized yet, do not throw error.
} else {
- ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(callable, args, argc, ce) + ".");
+ ERR_PRINT(vformat("Error calling from signal '%s' to callable: %s.", String(p_name), Variant::get_callable_error_text(callable, args, argc, ce)));
err = ERR_METHOD_NOT_FOUND;
}
}
@@ -1371,15 +1371,15 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
}
Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null.");
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is null.", p_signal));
if (p_callable.is_standard()) {
// FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes
// that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found
// and registered soon enough this branch is needed to allow `connect()` to succeed.
- ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
+ ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s' to callable '%s': the callable object is null.", p_signal, p_callable));
} else {
- ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is not valid: " + p_callable);
+ ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is not valid: '%s'.", p_signal, p_callable));
}
SignalData *s = signal_map.getptr(p_signal);
@@ -1400,7 +1400,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
#endif
}
- ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, "In Object of type '" + String(get_class()) + "': Attempt to connect nonexistent signal '" + p_signal + "' to callable '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, vformat("In Object of type '%s': Attempt to connect nonexistent signal '%s' to callable '%s'.", String(get_class()), p_signal, p_callable));
signal_map[p_signal] = SignalData();
s = &signal_map[p_signal];
@@ -1412,7 +1412,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
s->slot_map[*p_callable.get_base_comparator()].reference_count++;
return OK;
} else {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object.");
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("Signal '%s' is already connected to given callable '%s' in that object.", p_signal, p_callable));
}
}
@@ -1439,7 +1439,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
}
bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot determine if connected to '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot determine if connected to '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal);
@@ -1451,7 +1451,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return s->slot_map.has(*p_callable.get_base_comparator());
@@ -1469,7 +1469,7 @@ bool Object::has_connections(const StringName &p_signal) const {
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return !s->slot_map.is_empty();
@@ -1480,17 +1480,17 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
}
bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot disconnect from '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) ||
(!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal));
- ERR_FAIL_COND_V_MSG(signal_is_valid, false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(signal_is_valid, false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
}
- ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string()));
+ ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in '%s'.", p_signal, to_string()));
- ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
SignalData::Slot *slot = &s->slot_map[*p_callable.get_base_comparator()];
@@ -2128,7 +2128,7 @@ Object::~Object() {
if (_emitting) {
//@todo this may need to actually reach the debugger prioritarily somehow because it may crash before
- ERR_PRINT("Object " + to_string() + " was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.");
+ ERR_PRINT(vformat("Object '%s' was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.", to_string()));
}
// Drop all connections to the signals of this object.
diff --git a/core/object/object.h b/core/object/object.h
index 110d2790c5..8f93b75bd8 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -685,22 +685,22 @@ protected:
_ALWAYS_INLINE_ const ObjectGDExtension *_get_extension() const { return _extension; }
_ALWAYS_INLINE_ GDExtensionClassInstancePtr _get_extension_instance() const { return _extension_instance; }
virtual void _initialize_classv() { initialize_class(); }
- virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; };
- virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; };
- virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const {};
- virtual void _validate_propertyv(PropertyInfo &p_property) const {};
- virtual bool _property_can_revertv(const StringName &p_name) const { return false; };
- virtual bool _property_get_revertv(const StringName &p_name, Variant &r_property) const { return false; };
+ virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; }
+ virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; }
+ virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const {}
+ virtual void _validate_propertyv(PropertyInfo &p_property) const {}
+ virtual bool _property_can_revertv(const StringName &p_name) const { return false; }
+ virtual bool _property_get_revertv(const StringName &p_name, Variant &r_property) const { return false; }
virtual void _notificationv(int p_notification, bool p_reversed) {}
static void _bind_methods();
static void _bind_compatibility_methods() {}
- bool _set(const StringName &p_name, const Variant &p_property) { return false; };
- bool _get(const StringName &p_name, Variant &r_property) const { return false; };
- void _get_property_list(List<PropertyInfo> *p_list) const {};
- void _validate_property(PropertyInfo &p_property) const {};
- bool _property_can_revert(const StringName &p_name) const { return false; };
- bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return false; };
+ bool _set(const StringName &p_name, const Variant &p_property) { return false; }
+ bool _get(const StringName &p_name, Variant &r_property) const { return false; }
+ void _get_property_list(List<PropertyInfo> *p_list) const {}
+ void _validate_property(PropertyInfo &p_property) const {}
+ bool _property_can_revert(const StringName &p_name) const { return false; }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return false; }
void _notification(int p_notification) {}
_FORCE_INLINE_ static void (*_get_bind_methods())() {
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index c5856a8a81..542cb2b24d 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -239,9 +239,9 @@ Error ScriptServer::register_language(ScriptLanguage *p_language) {
ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more.");
for (int i = 0; i < _language_count; i++) {
const ScriptLanguage *other_language = _languages[i];
- ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, "A script language with extension '" + p_language->get_extension() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, "A script language with name '" + p_language->get_name() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, "A script language with type '" + p_language->get_type() + "' is already registered.");
+ ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, vformat("A script language with extension '%s' is already registered.", p_language->get_extension()));
+ ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, vformat("A script language with name '%s' is already registered.", p_language->get_name()));
+ ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, vformat("A script language with type '%s' is already registered.", p_language->get_type()));
}
_languages[_language_count++] = p_language;
return OK;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 3ddfbb3e7d..31d6638e58 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -446,8 +446,8 @@ public:
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override;
virtual void validate_property(PropertyInfo &p_property) const override {}
- virtual bool property_can_revert(const StringName &p_name) const override { return false; };
- virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; };
+ virtual bool property_can_revert(const StringName &p_name) const override { return false; }
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; }
virtual void get_method_list(List<MethodInfo> *p_list) const override;
virtual bool has_method(const StringName &p_method) const override;
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 03537dbeb1..7126852a5a 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -364,7 +364,7 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E, bool p_execu
Variant ret;
op.callable.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce));
+ ERR_PRINT(vformat("Error calling UndoRedo method operation '%s': %s.", String(op.name), Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)));
}
#ifdef TOOLS_ENABLED
Resource *res = Object::cast_to<Resource>(obj);
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 642de11a9f..59a0579ce3 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -352,7 +352,7 @@ void OS::ensure_user_data_dir() {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = da->make_dir_recursive(dd);
- ERR_FAIL_COND_MSG(err != OK, "Error attempting to create data dir: " + dd + ".");
+ ERR_FAIL_COND_MSG(err != OK, vformat("Error attempting to create data dir: %s.", dd));
}
String OS::get_model_name() const {
@@ -439,6 +439,11 @@ bool OS::has_feature(const String &p_feature) {
}
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64)
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
+#if defined(MACOS_ENABLED)
+ if (p_feature == "universal") {
+ return true;
+ }
+#endif
if (p_feature == "x86_64") {
return true;
}
@@ -452,6 +457,11 @@ bool OS::has_feature(const String &p_feature) {
}
#elif defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64)
#if defined(__aarch64__) || defined(_M_ARM64)
+#if defined(MACOS_ENABLED)
+ if (p_feature == "universal") {
+ return true;
+ }
+#endif
if (p_feature == "arm64") {
return true;
}
diff --git a/core/os/os.h b/core/os/os.h
index c42a39e0a4..4bb177eb77 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -176,14 +176,14 @@ public:
void set_delta_smoothing(bool p_enabled);
bool is_delta_smoothing_enabled() const;
- virtual Vector<String> get_system_fonts() const { return Vector<String>(); };
- virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); };
- virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
+ virtual Vector<String> get_system_fonts() const { return Vector<String>(); }
+ virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); }
+ virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }
virtual String get_executable_path() const;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
- virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
+ virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); }
virtual Error kill(const ProcessID &p_pid) = 0;
virtual int get_process_id() const;
virtual bool is_process_running(const ProcessID &p_pid) const = 0;
diff --git a/core/os/spin_lock.h b/core/os/spin_lock.h
index d386cd5890..8c2d5667ff 100644
--- a/core/os/spin_lock.h
+++ b/core/os/spin_lock.h
@@ -33,6 +33,10 @@
#include "core/typedefs.h"
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
#if defined(__APPLE__)
#include <os/lock.h>
@@ -52,19 +56,52 @@ public:
#else
+#include "core/os/thread.h"
+
#include <atomic>
-class SpinLock {
- mutable std::atomic_flag locked = ATOMIC_FLAG_INIT;
+_ALWAYS_INLINE_ static void _cpu_pause() {
+#if defined(_MSC_VER)
+// ----- MSVC.
+#if defined(_M_ARM) || defined(_M_ARM64) // ARM.
+ __yield();
+#elif defined(_M_IX86) || defined(_M_X64) // x86.
+ _mm_pause();
+#endif
+#elif defined(__GNUC__) || defined(__clang__)
+// ----- GCC/Clang.
+#if defined(__i386__) || defined(__x86_64__) // x86.
+ __builtin_ia32_pause();
+#elif defined(__arm__) || defined(__aarch64__) // ARM.
+ asm volatile("yield");
+#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) // PowerPC.
+ asm volatile("or 27,27,27");
+#elif defined(__riscv) // RISC-V.
+ asm volatile(".insn i 0x0F, 0, x0, x0, 0x010");
+#endif
+#endif
+}
+
+static_assert(std::atomic_bool::is_always_lock_free);
+
+class alignas(Thread::CACHE_LINE_BYTES) SpinLock {
+ mutable std::atomic<bool> locked = ATOMIC_VAR_INIT(false);
public:
_ALWAYS_INLINE_ void lock() const {
- while (locked.test_and_set(std::memory_order_acquire)) {
- // Continue.
+ while (true) {
+ bool expected = false;
+ if (locked.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_relaxed)) {
+ break;
+ }
+ do {
+ _cpu_pause();
+ } while (locked.load(std::memory_order_relaxed));
}
}
+
_ALWAYS_INLINE_ void unlock() const {
- locked.clear(std::memory_order_release);
+ locked.store(false, std::memory_order_release);
}
};
diff --git a/core/os/thread.h b/core/os/thread.h
index a0ecc24c91..1c442b41f6 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -42,6 +42,8 @@
#include "core/templates/safe_refcount.h"
#include "core/typedefs.h"
+#include <new>
+
#ifdef MINGW_ENABLED
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#include "thirdparty/mingw-std-threads/mingw.thread.h"
@@ -85,6 +87,20 @@ public:
void (*term)() = nullptr;
};
+#if defined(__cpp_lib_hardware_interference_size) && !defined(ANDROID_ENABLED) // This would be OK with NDK >= 26.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winterference-size"
+#endif
+ static constexpr size_t CACHE_LINE_BYTES = std::hardware_destructive_interference_size;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+#else
+ // At a negligible memory cost, we use a conservatively high value.
+ static constexpr size_t CACHE_LINE_BYTES = 128;
+#endif
+
private:
friend class Main;
@@ -135,6 +151,8 @@ public:
typedef uint64_t ID;
+ static constexpr size_t CACHE_LINE_BYTES = sizeof(void *);
+
enum : ID {
UNASSIGNED_ID = 0,
MAIN_ID = 1
diff --git a/core/string/char_range.inc b/core/string/char_range.inc
index 2b081b96de..efae757802 100644
--- a/core/string/char_range.inc
+++ b/core/string/char_range.inc
@@ -33,14 +33,17 @@
#include "core/typedefs.h"
+// Unicode Derived Core Properties
+// Source: https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt
+
struct CharRange {
char32_t start;
char32_t end;
};
-inline constexpr CharRange xid_start[] = {
+constexpr inline CharRange xid_start[] = {
{ 0x41, 0x5a },
- { 0x5f, 0x5f },
+ { 0x5f, 0x5f }, // Underscore technically isn't in XID_Start, but for our purposes it's included.
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
{ 0xb5, 0xb5 },
@@ -54,7 +57,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x2ee, 0x2ee },
{ 0x370, 0x374 },
{ 0x376, 0x377 },
- { 0x37a, 0x37d },
+ { 0x37b, 0x37d },
{ 0x37f, 0x37f },
{ 0x386, 0x386 },
{ 0x388, 0x38a },
@@ -182,7 +185,7 @@ inline constexpr CharRange xid_start[] = {
{ 0xdbd, 0xdbd },
{ 0xdc0, 0xdc6 },
{ 0xe01, 0xe30 },
- { 0xe32, 0xe33 },
+ { 0xe32, 0xe32 },
{ 0xe40, 0xe46 },
{ 0xe81, 0xe82 },
{ 0xe84, 0xe84 },
@@ -190,7 +193,7 @@ inline constexpr CharRange xid_start[] = {
{ 0xe8c, 0xea3 },
{ 0xea5, 0xea5 },
{ 0xea7, 0xeb0 },
- { 0xeb2, 0xeb3 },
+ { 0xeb2, 0xeb2 },
{ 0xebd, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
@@ -245,8 +248,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x17d7, 0x17d7 },
{ 0x17dc, 0x17dc },
{ 0x1820, 0x1878 },
- { 0x1880, 0x1884 },
- { 0x1887, 0x18a8 },
+ { 0x1880, 0x18a8 },
{ 0x18aa, 0x18aa },
{ 0x18b0, 0x18f5 },
{ 0x1900, 0x191e },
@@ -265,7 +267,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x1c00, 0x1c23 },
{ 0x1c4d, 0x1c4f },
{ 0x1c5a, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1ce9, 0x1cec },
@@ -330,7 +332,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x3031, 0x3035 },
{ 0x3038, 0x303c },
{ 0x3041, 0x3096 },
- { 0x309b, 0x309f },
+ { 0x309d, 0x309f },
{ 0x30a1, 0x30fa },
{ 0x30fc, 0x30ff },
{ 0x3105, 0x312f },
@@ -348,10 +350,10 @@ inline constexpr CharRange xid_start[] = {
{ 0xa6a0, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
+ { 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa801 },
{ 0xa803, 0xa805 },
{ 0xa807, 0xa80a },
@@ -406,15 +408,22 @@ inline constexpr CharRange xid_start[] = {
{ 0xfb40, 0xfb41 },
{ 0xfb43, 0xfb44 },
{ 0xfb46, 0xfbb1 },
- { 0xfbd3, 0xfd3d },
+ { 0xfbd3, 0xfc5d },
+ { 0xfc64, 0xfd3d },
{ 0xfd50, 0xfd8f },
{ 0xfd92, 0xfdc7 },
- { 0xfdf0, 0xfdfb },
- { 0xfe70, 0xfe74 },
- { 0xfe76, 0xfefc },
+ { 0xfdf0, 0xfdf9 },
+ { 0xfe71, 0xfe71 },
+ { 0xfe73, 0xfe73 },
+ { 0xfe77, 0xfe77 },
+ { 0xfe79, 0xfe79 },
+ { 0xfe7b, 0xfe7b },
+ { 0xfe7d, 0xfe7d },
+ { 0xfe7f, 0xfefc },
{ 0xff21, 0xff3a },
{ 0xff41, 0xff5a },
- { 0xff66, 0xffbe },
+ { 0xff66, 0xff9d },
+ { 0xffa0, 0xffbe },
{ 0xffc2, 0xffc7 },
{ 0xffca, 0xffcf },
{ 0xffd2, 0xffd7 },
@@ -449,6 +458,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -485,8 +495,11 @@ inline constexpr CharRange xid_start[] = {
{ 0x10c80, 0x10cb2 },
{ 0x10cc0, 0x10cf2 },
{ 0x10d00, 0x10d23 },
+ { 0x10d4a, 0x10d65 },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
{ 0x10eb0, 0x10eb1 },
+ { 0x10ec2, 0x10ec4 },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
@@ -509,6 +522,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
{ 0x11213, 0x1122b },
+ { 0x1123f, 0x11240 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
@@ -524,6 +538,13 @@ inline constexpr CharRange xid_start[] = {
{ 0x1133d, 0x1133d },
{ 0x11350, 0x11350 },
{ 0x1135d, 0x11361 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113b7 },
+ { 0x113d1, 0x113d1 },
+ { 0x113d3, 0x113d3 },
{ 0x11400, 0x11434 },
{ 0x11447, 0x1144a },
{ 0x1145f, 0x11461 },
@@ -558,6 +579,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x11a5c, 0x11a89 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
{ 0x11c00, 0x11c08 },
{ 0x11c0a, 0x11c2e },
{ 0x11c40, 0x11c40 },
@@ -571,13 +593,19 @@ inline constexpr CharRange xid_start[] = {
{ 0x11d6a, 0x11d89 },
{ 0x11d98, 0x11d98 },
{ 0x11ee0, 0x11ef2 },
+ { 0x11f02, 0x11f02 },
+ { 0x11f04, 0x11f10 },
+ { 0x11f12, 0x11f33 },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
{ 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
- { 0x13000, 0x1342e },
+ { 0x13000, 0x1342f },
+ { 0x13441, 0x13446 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x1611d },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a70, 0x16abe },
@@ -586,6 +614,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x16b40, 0x16b43 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
{ 0x16f50, 0x16f50 },
@@ -594,12 +623,14 @@ inline constexpr CharRange xid_start[] = {
{ 0x16fe3, 0x16fe3 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
{ 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
{ 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
{ 0x1b164, 0x1b167 },
{ 0x1b170, 0x1b2fb },
{ 0x1bc00, 0x1bc6a },
@@ -637,11 +668,16 @@ inline constexpr CharRange xid_start[] = {
{ 0x1d7aa, 0x1d7c2 },
{ 0x1d7c4, 0x1d7cb },
{ 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
{ 0x1e100, 0x1e12c },
{ 0x1e137, 0x1e13d },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ad },
{ 0x1e2c0, 0x1e2eb },
+ { 0x1e4d0, 0x1e4eb },
+ { 0x1e5d0, 0x1e5ed },
+ { 0x1e5f0, 0x1e5f0 },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@@ -683,15 +719,17 @@ inline constexpr CharRange xid_start[] = {
{ 0x1eea5, 0x1eea9 },
{ 0x1eeab, 0x1eebb },
{ 0x20000, 0x2a6df },
- { 0x2a700, 0x2b738 },
+ { 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
};
-inline constexpr CharRange xid_continue[] = {
+constexpr inline CharRange xid_continue[] = {
{ 0x30, 0x39 },
{ 0x41, 0x5a },
{ 0x5f, 0x5f },
@@ -709,7 +747,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x2ee, 0x2ee },
{ 0x300, 0x374 },
{ 0x376, 0x377 },
- { 0x37a, 0x37d },
+ { 0x37b, 0x37d },
{ 0x37f, 0x37f },
{ 0x386, 0x38a },
{ 0x38c, 0x38c },
@@ -745,7 +783,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
- { 0x898, 0x8e1 },
+ { 0x897, 0x8e1 },
{ 0x8e3, 0x963 },
{ 0x966, 0x96f },
{ 0x971, 0x983 },
@@ -850,7 +888,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0xcdd, 0xcde },
{ 0xce0, 0xce3 },
{ 0xce6, 0xcef },
- { 0xcf1, 0xcf2 },
+ { 0xcf1, 0xcf3 },
{ 0xd00, 0xd0c },
{ 0xd0e, 0xd10 },
{ 0xd12, 0xd44 },
@@ -883,7 +921,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0xea7, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
- { 0xec8, 0xecd },
+ { 0xec8, 0xece },
{ 0xed0, 0xed9 },
{ 0xedc, 0xedf },
{ 0xf00, 0xf00 },
@@ -921,8 +959,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1312, 0x1315 },
{ 0x1318, 0x135a },
{ 0x135d, 0x135f },
- { 0x1369, 0x1369 },
- { 0x1371, 0x1371 },
+ { 0x1369, 0x1371 },
{ 0x1380, 0x138f },
{ 0x13a0, 0x13f5 },
{ 0x13f8, 0x13fd },
@@ -969,7 +1006,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1c00, 0x1c37 },
{ 0x1c40, 0x1c49 },
{ 0x1c4d, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1cd0, 0x1cd2 },
@@ -993,6 +1030,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1fe0, 0x1fec },
{ 0x1ff2, 0x1ff4 },
{ 0x1ff6, 0x1ffc },
+ { 0x200c, 0x200d },
{ 0x203f, 0x2040 },
{ 0x2054, 0x2054 },
{ 0x2071, 0x2071 },
@@ -1036,9 +1074,9 @@ inline constexpr CharRange xid_continue[] = {
{ 0x3031, 0x3035 },
{ 0x3038, 0x303c },
{ 0x3041, 0x3096 },
- { 0x3099, 0x309f },
- { 0x30a1, 0x30fa },
- { 0x30fc, 0x30ff },
+ { 0x3099, 0x309a },
+ { 0x309d, 0x309f },
+ { 0x30a1, 0x30ff },
{ 0x3105, 0x312f },
{ 0x3131, 0x318e },
{ 0x31a0, 0x31bf },
@@ -1053,10 +1091,10 @@ inline constexpr CharRange xid_continue[] = {
{ 0xa67f, 0xa6f1 },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
+ { 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa827 },
{ 0xa82c, 0xa82c },
{ 0xa840, 0xa873 },
@@ -1102,21 +1140,27 @@ inline constexpr CharRange xid_continue[] = {
{ 0xfb40, 0xfb41 },
{ 0xfb43, 0xfb44 },
{ 0xfb46, 0xfbb1 },
- { 0xfbd3, 0xfd3d },
+ { 0xfbd3, 0xfc5d },
+ { 0xfc64, 0xfd3d },
{ 0xfd50, 0xfd8f },
{ 0xfd92, 0xfdc7 },
- { 0xfdf0, 0xfdfb },
+ { 0xfdf0, 0xfdf9 },
{ 0xfe00, 0xfe0f },
{ 0xfe20, 0xfe2f },
{ 0xfe33, 0xfe34 },
{ 0xfe4d, 0xfe4f },
- { 0xfe70, 0xfe74 },
- { 0xfe76, 0xfefc },
+ { 0xfe71, 0xfe71 },
+ { 0xfe73, 0xfe73 },
+ { 0xfe77, 0xfe77 },
+ { 0xfe79, 0xfe79 },
+ { 0xfe7b, 0xfe7b },
+ { 0xfe7d, 0xfe7d },
+ { 0xfe7f, 0xfefc },
{ 0xff10, 0xff19 },
{ 0xff21, 0xff3a },
{ 0xff3f, 0xff3f },
{ 0xff41, 0xff5a },
- { 0xff66, 0xffbe },
+ { 0xff65, 0xffbe },
{ 0xffc2, 0xffc7 },
{ 0xffca, 0xffcf },
{ 0xffd2, 0xffd7 },
@@ -1154,6 +1198,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -1194,10 +1239,14 @@ inline constexpr CharRange xid_continue[] = {
{ 0x10cc0, 0x10cf2 },
{ 0x10d00, 0x10d27 },
{ 0x10d30, 0x10d39 },
+ { 0x10d40, 0x10d65 },
+ { 0x10d69, 0x10d6d },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
{ 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
- { 0x10f00, 0x10f1c },
+ { 0x10ec2, 0x10ec4 },
+ { 0x10efc, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f50 },
{ 0x10f70, 0x10f85 },
@@ -1220,7 +1269,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
{ 0x11213, 0x11237 },
- { 0x1123e, 0x1123e },
+ { 0x1123e, 0x11241 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
@@ -1243,6 +1292,16 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1135d, 0x11363 },
{ 0x11366, 0x1136c },
{ 0x11370, 0x11374 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113c0 },
+ { 0x113c2, 0x113c2 },
+ { 0x113c5, 0x113c5 },
+ { 0x113c7, 0x113ca },
+ { 0x113cc, 0x113d3 },
+ { 0x113e1, 0x113e2 },
{ 0x11400, 0x1144a },
{ 0x11450, 0x11459 },
{ 0x1145e, 0x11461 },
@@ -1257,6 +1316,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11650, 0x11659 },
{ 0x11680, 0x116b8 },
{ 0x116c0, 0x116c9 },
+ { 0x116d0, 0x116e3 },
{ 0x11700, 0x1171a },
{ 0x1171d, 0x1172b },
{ 0x11730, 0x11739 },
@@ -1280,6 +1340,8 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11a50, 0x11a99 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
+ { 0x11bf0, 0x11bf9 },
{ 0x11c00, 0x11c08 },
{ 0x11c0a, 0x11c36 },
{ 0x11c38, 0x11c40 },
@@ -1301,13 +1363,20 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11d93, 0x11d98 },
{ 0x11da0, 0x11da9 },
{ 0x11ee0, 0x11ef6 },
+ { 0x11f00, 0x11f10 },
+ { 0x11f12, 0x11f3a },
+ { 0x11f3e, 0x11f42 },
+ { 0x11f50, 0x11f5a },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
{ 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
- { 0x13000, 0x1342e },
+ { 0x13000, 0x1342f },
+ { 0x13440, 0x13455 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x16139 },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a60, 0x16a69 },
@@ -1320,6 +1389,8 @@ inline constexpr CharRange xid_continue[] = {
{ 0x16b50, 0x16b59 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
+ { 0x16d70, 0x16d79 },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
{ 0x16f4f, 0x16f87 },
@@ -1329,12 +1400,14 @@ inline constexpr CharRange xid_continue[] = {
{ 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
{ 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
{ 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
{ 0x1b164, 0x1b167 },
{ 0x1b170, 0x1b2fb },
{ 0x1bc00, 0x1bc6a },
@@ -1342,6 +1415,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1bc80, 0x1bc88 },
{ 0x1bc90, 0x1bc99 },
{ 0x1bc9d, 0x1bc9e },
+ { 0x1ccf0, 0x1ccf9 },
{ 0x1cf00, 0x1cf2d },
{ 0x1cf30, 0x1cf46 },
{ 0x1d165, 0x1d169 },
@@ -1388,17 +1462,22 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1da9b, 0x1da9f },
{ 0x1daa1, 0x1daaf },
{ 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
{ 0x1e000, 0x1e006 },
{ 0x1e008, 0x1e018 },
{ 0x1e01b, 0x1e021 },
{ 0x1e023, 0x1e024 },
{ 0x1e026, 0x1e02a },
+ { 0x1e030, 0x1e06d },
+ { 0x1e08f, 0x1e08f },
{ 0x1e100, 0x1e12c },
{ 0x1e130, 0x1e13d },
{ 0x1e140, 0x1e149 },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ae },
{ 0x1e2c0, 0x1e2f9 },
+ { 0x1e4d0, 0x1e4f9 },
+ { 0x1e5d0, 0x1e5fa },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@@ -1442,16 +1521,18 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1eeab, 0x1eebb },
{ 0x1fbf0, 0x1fbf9 },
{ 0x20000, 0x2a6df },
- { 0x2a700, 0x2b738 },
+ { 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
{ 0xe0100, 0xe01ef },
};
-inline constexpr CharRange uppercase_letter[] = {
+constexpr inline CharRange uppercase_letter[] = {
{ 0x41, 0x5a },
{ 0xc0, 0xd6 },
{ 0xd8, 0xde },
@@ -1728,6 +1809,7 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x10c7, 0x10c7 },
{ 0x10cd, 0x10cd },
{ 0x13a0, 0x13f5 },
+ { 0x1c89, 0x1c89 },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1e00, 0x1e00 },
@@ -1882,7 +1964,9 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x2130, 0x2133 },
{ 0x213e, 0x213f },
{ 0x2145, 0x2145 },
+ { 0x2160, 0x216f },
{ 0x2183, 0x2183 },
+ { 0x24b6, 0x24cf },
{ 0x2c00, 0x2c2f },
{ 0x2c60, 0x2c60 },
{ 0x2c62, 0x2c64 },
@@ -2052,9 +2136,12 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0xa7c2, 0xa7c2 },
{ 0xa7c4, 0xa7c7 },
{ 0xa7c9, 0xa7c9 },
+ { 0xa7cb, 0xa7cc },
{ 0xa7d0, 0xa7d0 },
{ 0xa7d6, 0xa7d6 },
{ 0xa7d8, 0xa7d8 },
+ { 0xa7da, 0xa7da },
+ { 0xa7dc, 0xa7dc },
{ 0xa7f5, 0xa7f5 },
{ 0xff21, 0xff3a },
{ 0x10400, 0x10427 },
@@ -2064,6 +2151,7 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x1058c, 0x10592 },
{ 0x10594, 0x10595 },
{ 0x10c80, 0x10cb2 },
+ { 0x10d50, 0x10d65 },
{ 0x118a0, 0x118bf },
{ 0x16e40, 0x16e5f },
{ 0x1d400, 0x1d419 },
@@ -2098,11 +2186,16 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x1d790, 0x1d7a8 },
{ 0x1d7ca, 0x1d7ca },
{ 0x1e900, 0x1e921 },
+ { 0x1f130, 0x1f149 },
+ { 0x1f150, 0x1f169 },
+ { 0x1f170, 0x1f189 },
};
-inline constexpr CharRange lowercase_letter[] = {
+constexpr inline CharRange lowercase_letter[] = {
{ 0x61, 0x7a },
+ { 0xaa, 0xaa },
{ 0xb5, 0xb5 },
+ { 0xba, 0xba },
{ 0xdf, 0xf6 },
{ 0xf8, 0xff },
{ 0x101, 0x101 },
@@ -2246,11 +2339,14 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x24b, 0x24b },
{ 0x24d, 0x24d },
{ 0x24f, 0x293 },
- { 0x295, 0x2af },
+ { 0x295, 0x2b8 },
+ { 0x2c0, 0x2c1 },
+ { 0x2e0, 0x2e4 },
+ { 0x345, 0x345 },
{ 0x371, 0x371 },
{ 0x373, 0x373 },
{ 0x377, 0x377 },
- { 0x37b, 0x37d },
+ { 0x37a, 0x37d },
{ 0x390, 0x390 },
{ 0x3ac, 0x3ce },
{ 0x3d0, 0x3d1 },
@@ -2372,12 +2468,11 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x52f, 0x52f },
{ 0x560, 0x588 },
{ 0x10d0, 0x10fa },
- { 0x10fd, 0x10ff },
+ { 0x10fc, 0x10ff },
{ 0x13f8, 0x13fd },
{ 0x1c80, 0x1c88 },
- { 0x1d00, 0x1d2b },
- { 0x1d6b, 0x1d77 },
- { 0x1d79, 0x1d9a },
+ { 0x1c8a, 0x1c8a },
+ { 0x1d00, 0x1dbf },
{ 0x1e01, 0x1e01 },
{ 0x1e03, 0x1e03 },
{ 0x1e05, 0x1e05 },
@@ -2522,6 +2617,9 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1fe0, 0x1fe7 },
{ 0x1ff2, 0x1ff4 },
{ 0x1ff6, 0x1ff7 },
+ { 0x2071, 0x2071 },
+ { 0x207f, 0x207f },
+ { 0x2090, 0x209c },
{ 0x210a, 0x210a },
{ 0x210e, 0x210f },
{ 0x2113, 0x2113 },
@@ -2531,7 +2629,9 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x213c, 0x213d },
{ 0x2146, 0x2149 },
{ 0x214e, 0x214e },
+ { 0x2170, 0x217f },
{ 0x2184, 0x2184 },
+ { 0x24d0, 0x24e9 },
{ 0x2c30, 0x2c5f },
{ 0x2c61, 0x2c61 },
{ 0x2c65, 0x2c66 },
@@ -2540,7 +2640,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x2c6c, 0x2c6c },
{ 0x2c71, 0x2c71 },
{ 0x2c73, 0x2c74 },
- { 0x2c76, 0x2c7b },
+ { 0x2c76, 0x2c7d },
{ 0x2c81, 0x2c81 },
{ 0x2c83, 0x2c83 },
{ 0x2c85, 0x2c85 },
@@ -2633,7 +2733,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa695, 0xa695 },
{ 0xa697, 0xa697 },
{ 0xa699, 0xa699 },
- { 0xa69b, 0xa69b },
+ { 0xa69b, 0xa69d },
{ 0xa723, 0xa723 },
{ 0xa725, 0xa725 },
{ 0xa727, 0xa727 },
@@ -2671,8 +2771,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa769, 0xa769 },
{ 0xa76b, 0xa76b },
{ 0xa76d, 0xa76d },
- { 0xa76f, 0xa76f },
- { 0xa771, 0xa778 },
+ { 0xa76f, 0xa778 },
{ 0xa77a, 0xa77a },
{ 0xa77c, 0xa77c },
{ 0xa77f, 0xa77f },
@@ -2705,15 +2804,18 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa7c3, 0xa7c3 },
{ 0xa7c8, 0xa7c8 },
{ 0xa7ca, 0xa7ca },
+ { 0xa7cd, 0xa7cd },
{ 0xa7d1, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7d5 },
{ 0xa7d7, 0xa7d7 },
{ 0xa7d9, 0xa7d9 },
+ { 0xa7db, 0xa7db },
+ { 0xa7f2, 0xa7f4 },
{ 0xa7f6, 0xa7f6 },
- { 0xa7fa, 0xa7fa },
+ { 0xa7f8, 0xa7fa },
{ 0xab30, 0xab5a },
- { 0xab60, 0xab68 },
+ { 0xab5c, 0xab69 },
{ 0xab70, 0xabbf },
{ 0xfb00, 0xfb06 },
{ 0xfb13, 0xfb17 },
@@ -2724,7 +2826,12 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x10780, 0x10780 },
+ { 0x10783, 0x10785 },
+ { 0x10787, 0x107b0 },
+ { 0x107b2, 0x107ba },
{ 0x10cc0, 0x10cf2 },
+ { 0x10d70, 0x10d85 },
{ 0x118c0, 0x118df },
{ 0x16e60, 0x16e7f },
{ 0x1d41a, 0x1d433 },
@@ -2758,10 +2865,11 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1df00, 0x1df09 },
{ 0x1df0b, 0x1df1e },
{ 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
{ 0x1e922, 0x1e943 },
};
-inline constexpr CharRange unicode_letter[] = {
+constexpr inline CharRange unicode_letter[] = {
{ 0x41, 0x5a },
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
@@ -2774,7 +2882,8 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x2e0, 0x2e4 },
{ 0x2ec, 0x2ec },
{ 0x2ee, 0x2ee },
- { 0x370, 0x374 },
+ { 0x345, 0x345 },
+ { 0x363, 0x374 },
{ 0x376, 0x377 },
{ 0x37a, 0x37d },
{ 0x37f, 0x37f },
@@ -2788,49 +2897,58 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x531, 0x556 },
{ 0x559, 0x559 },
{ 0x560, 0x588 },
+ { 0x5b0, 0x5bd },
+ { 0x5bf, 0x5bf },
+ { 0x5c1, 0x5c2 },
+ { 0x5c4, 0x5c5 },
+ { 0x5c7, 0x5c7 },
{ 0x5d0, 0x5ea },
{ 0x5ef, 0x5f2 },
- { 0x620, 0x64a },
- { 0x66e, 0x66f },
- { 0x671, 0x6d3 },
- { 0x6d5, 0x6d5 },
- { 0x6e5, 0x6e6 },
- { 0x6ee, 0x6ef },
+ { 0x610, 0x61a },
+ { 0x620, 0x657 },
+ { 0x659, 0x65f },
+ { 0x66e, 0x6d3 },
+ { 0x6d5, 0x6dc },
+ { 0x6e1, 0x6e8 },
+ { 0x6ed, 0x6ef },
{ 0x6fa, 0x6fc },
{ 0x6ff, 0x6ff },
- { 0x710, 0x710 },
- { 0x712, 0x72f },
- { 0x74d, 0x7a5 },
- { 0x7b1, 0x7b1 },
+ { 0x710, 0x73f },
+ { 0x74d, 0x7b1 },
{ 0x7ca, 0x7ea },
{ 0x7f4, 0x7f5 },
{ 0x7fa, 0x7fa },
- { 0x800, 0x815 },
- { 0x81a, 0x81a },
- { 0x824, 0x824 },
- { 0x828, 0x828 },
+ { 0x800, 0x817 },
+ { 0x81a, 0x82c },
{ 0x840, 0x858 },
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
+ { 0x897, 0x897 },
{ 0x8a0, 0x8c9 },
- { 0x904, 0x939 },
- { 0x93d, 0x93d },
- { 0x950, 0x950 },
- { 0x958, 0x961 },
- { 0x971, 0x980 },
+ { 0x8d4, 0x8df },
+ { 0x8e3, 0x8e9 },
+ { 0x8f0, 0x93b },
+ { 0x93d, 0x94c },
+ { 0x94e, 0x950 },
+ { 0x955, 0x963 },
+ { 0x971, 0x983 },
{ 0x985, 0x98c },
{ 0x98f, 0x990 },
{ 0x993, 0x9a8 },
{ 0x9aa, 0x9b0 },
{ 0x9b2, 0x9b2 },
{ 0x9b6, 0x9b9 },
- { 0x9bd, 0x9bd },
+ { 0x9bd, 0x9c4 },
+ { 0x9c7, 0x9c8 },
+ { 0x9cb, 0x9cc },
{ 0x9ce, 0x9ce },
+ { 0x9d7, 0x9d7 },
{ 0x9dc, 0x9dd },
- { 0x9df, 0x9e1 },
+ { 0x9df, 0x9e3 },
{ 0x9f0, 0x9f1 },
{ 0x9fc, 0x9fc },
+ { 0xa01, 0xa03 },
{ 0xa05, 0xa0a },
{ 0xa0f, 0xa10 },
{ 0xa13, 0xa28 },
@@ -2838,30 +2956,41 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xa32, 0xa33 },
{ 0xa35, 0xa36 },
{ 0xa38, 0xa39 },
+ { 0xa3e, 0xa42 },
+ { 0xa47, 0xa48 },
+ { 0xa4b, 0xa4c },
+ { 0xa51, 0xa51 },
{ 0xa59, 0xa5c },
{ 0xa5e, 0xa5e },
- { 0xa72, 0xa74 },
+ { 0xa70, 0xa75 },
+ { 0xa81, 0xa83 },
{ 0xa85, 0xa8d },
{ 0xa8f, 0xa91 },
{ 0xa93, 0xaa8 },
{ 0xaaa, 0xab0 },
{ 0xab2, 0xab3 },
{ 0xab5, 0xab9 },
- { 0xabd, 0xabd },
+ { 0xabd, 0xac5 },
+ { 0xac7, 0xac9 },
+ { 0xacb, 0xacc },
{ 0xad0, 0xad0 },
- { 0xae0, 0xae1 },
- { 0xaf9, 0xaf9 },
+ { 0xae0, 0xae3 },
+ { 0xaf9, 0xafc },
+ { 0xb01, 0xb03 },
{ 0xb05, 0xb0c },
{ 0xb0f, 0xb10 },
{ 0xb13, 0xb28 },
{ 0xb2a, 0xb30 },
{ 0xb32, 0xb33 },
{ 0xb35, 0xb39 },
- { 0xb3d, 0xb3d },
+ { 0xb3d, 0xb44 },
+ { 0xb47, 0xb48 },
+ { 0xb4b, 0xb4c },
+ { 0xb56, 0xb57 },
{ 0xb5c, 0xb5d },
- { 0xb5f, 0xb61 },
+ { 0xb5f, 0xb63 },
{ 0xb71, 0xb71 },
- { 0xb83, 0xb83 },
+ { 0xb82, 0xb83 },
{ 0xb85, 0xb8a },
{ 0xb8e, 0xb90 },
{ 0xb92, 0xb95 },
@@ -2871,65 +3000,80 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xba3, 0xba4 },
{ 0xba8, 0xbaa },
{ 0xbae, 0xbb9 },
+ { 0xbbe, 0xbc2 },
+ { 0xbc6, 0xbc8 },
+ { 0xbca, 0xbcc },
{ 0xbd0, 0xbd0 },
- { 0xc05, 0xc0c },
+ { 0xbd7, 0xbd7 },
+ { 0xc00, 0xc0c },
{ 0xc0e, 0xc10 },
{ 0xc12, 0xc28 },
{ 0xc2a, 0xc39 },
- { 0xc3d, 0xc3d },
+ { 0xc3d, 0xc44 },
+ { 0xc46, 0xc48 },
+ { 0xc4a, 0xc4c },
+ { 0xc55, 0xc56 },
{ 0xc58, 0xc5a },
{ 0xc5d, 0xc5d },
- { 0xc60, 0xc61 },
- { 0xc80, 0xc80 },
+ { 0xc60, 0xc63 },
+ { 0xc80, 0xc83 },
{ 0xc85, 0xc8c },
{ 0xc8e, 0xc90 },
{ 0xc92, 0xca8 },
{ 0xcaa, 0xcb3 },
{ 0xcb5, 0xcb9 },
- { 0xcbd, 0xcbd },
+ { 0xcbd, 0xcc4 },
+ { 0xcc6, 0xcc8 },
+ { 0xcca, 0xccc },
+ { 0xcd5, 0xcd6 },
{ 0xcdd, 0xcde },
- { 0xce0, 0xce1 },
- { 0xcf1, 0xcf2 },
- { 0xd04, 0xd0c },
+ { 0xce0, 0xce3 },
+ { 0xcf1, 0xcf3 },
+ { 0xd00, 0xd0c },
{ 0xd0e, 0xd10 },
{ 0xd12, 0xd3a },
- { 0xd3d, 0xd3d },
+ { 0xd3d, 0xd44 },
+ { 0xd46, 0xd48 },
+ { 0xd4a, 0xd4c },
{ 0xd4e, 0xd4e },
- { 0xd54, 0xd56 },
- { 0xd5f, 0xd61 },
+ { 0xd54, 0xd57 },
+ { 0xd5f, 0xd63 },
{ 0xd7a, 0xd7f },
+ { 0xd81, 0xd83 },
{ 0xd85, 0xd96 },
{ 0xd9a, 0xdb1 },
{ 0xdb3, 0xdbb },
{ 0xdbd, 0xdbd },
{ 0xdc0, 0xdc6 },
- { 0xe01, 0xe30 },
- { 0xe32, 0xe33 },
+ { 0xdcf, 0xdd4 },
+ { 0xdd6, 0xdd6 },
+ { 0xdd8, 0xddf },
+ { 0xdf2, 0xdf3 },
+ { 0xe01, 0xe3a },
{ 0xe40, 0xe46 },
+ { 0xe4d, 0xe4d },
{ 0xe81, 0xe82 },
{ 0xe84, 0xe84 },
{ 0xe86, 0xe8a },
{ 0xe8c, 0xea3 },
{ 0xea5, 0xea5 },
- { 0xea7, 0xeb0 },
- { 0xeb2, 0xeb3 },
- { 0xebd, 0xebd },
+ { 0xea7, 0xeb9 },
+ { 0xebb, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
+ { 0xecd, 0xecd },
{ 0xedc, 0xedf },
{ 0xf00, 0xf00 },
{ 0xf40, 0xf47 },
{ 0xf49, 0xf6c },
- { 0xf88, 0xf8c },
- { 0x1000, 0x102a },
- { 0x103f, 0x103f },
- { 0x1050, 0x1055 },
- { 0x105a, 0x105d },
- { 0x1061, 0x1061 },
- { 0x1065, 0x1066 },
- { 0x106e, 0x1070 },
- { 0x1075, 0x1081 },
- { 0x108e, 0x108e },
+ { 0xf71, 0xf83 },
+ { 0xf88, 0xf97 },
+ { 0xf99, 0xfbc },
+ { 0x1000, 0x1036 },
+ { 0x1038, 0x1038 },
+ { 0x103b, 0x103f },
+ { 0x1050, 0x108f },
+ { 0x109a, 0x109d },
{ 0x10a0, 0x10c5 },
{ 0x10c7, 0x10c7 },
{ 0x10cd, 0x10cd },
@@ -2957,37 +3101,44 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x166f, 0x167f },
{ 0x1681, 0x169a },
{ 0x16a0, 0x16ea },
- { 0x16f1, 0x16f8 },
- { 0x1700, 0x1711 },
- { 0x171f, 0x1731 },
- { 0x1740, 0x1751 },
+ { 0x16ee, 0x16f8 },
+ { 0x1700, 0x1713 },
+ { 0x171f, 0x1733 },
+ { 0x1740, 0x1753 },
{ 0x1760, 0x176c },
{ 0x176e, 0x1770 },
+ { 0x1772, 0x1773 },
{ 0x1780, 0x17b3 },
+ { 0x17b6, 0x17c8 },
{ 0x17d7, 0x17d7 },
{ 0x17dc, 0x17dc },
{ 0x1820, 0x1878 },
- { 0x1880, 0x1884 },
- { 0x1887, 0x18a8 },
- { 0x18aa, 0x18aa },
+ { 0x1880, 0x18aa },
{ 0x18b0, 0x18f5 },
{ 0x1900, 0x191e },
+ { 0x1920, 0x192b },
+ { 0x1930, 0x1938 },
{ 0x1950, 0x196d },
{ 0x1970, 0x1974 },
{ 0x1980, 0x19ab },
{ 0x19b0, 0x19c9 },
- { 0x1a00, 0x1a16 },
- { 0x1a20, 0x1a54 },
+ { 0x1a00, 0x1a1b },
+ { 0x1a20, 0x1a5e },
+ { 0x1a61, 0x1a74 },
{ 0x1aa7, 0x1aa7 },
- { 0x1b05, 0x1b33 },
+ { 0x1abf, 0x1ac0 },
+ { 0x1acc, 0x1ace },
+ { 0x1b00, 0x1b33 },
+ { 0x1b35, 0x1b43 },
{ 0x1b45, 0x1b4c },
- { 0x1b83, 0x1ba0 },
- { 0x1bae, 0x1baf },
+ { 0x1b80, 0x1ba9 },
+ { 0x1bac, 0x1baf },
{ 0x1bba, 0x1be5 },
- { 0x1c00, 0x1c23 },
+ { 0x1be7, 0x1bf1 },
+ { 0x1c00, 0x1c36 },
{ 0x1c4d, 0x1c4f },
{ 0x1c5a, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1ce9, 0x1cec },
@@ -2995,6 +3146,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1cf5, 0x1cf6 },
{ 0x1cfa, 0x1cfa },
{ 0x1d00, 0x1dbf },
+ { 0x1dd3, 0x1df4 },
{ 0x1e00, 0x1f15 },
{ 0x1f18, 0x1f1d },
{ 0x1f20, 0x1f45 },
@@ -3030,7 +3182,8 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x213c, 0x213f },
{ 0x2145, 0x2149 },
{ 0x214e, 0x214e },
- { 0x2183, 0x2184 },
+ { 0x2160, 0x2188 },
+ { 0x24b6, 0x24e9 },
{ 0x2c00, 0x2ce4 },
{ 0x2ceb, 0x2cee },
{ 0x2cf2, 0x2cf3 },
@@ -3048,10 +3201,12 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x2dc8, 0x2dce },
{ 0x2dd0, 0x2dd6 },
{ 0x2dd8, 0x2dde },
+ { 0x2de0, 0x2dff },
{ 0x2e2f, 0x2e2f },
- { 0x3005, 0x3006 },
+ { 0x3005, 0x3007 },
+ { 0x3021, 0x3029 },
{ 0x3031, 0x3035 },
- { 0x303b, 0x303c },
+ { 0x3038, 0x303c },
{ 0x3041, 0x3096 },
{ 0x309d, 0x309f },
{ 0x30a1, 0x30fa },
@@ -3067,45 +3222,39 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xa610, 0xa61f },
{ 0xa62a, 0xa62b },
{ 0xa640, 0xa66e },
- { 0xa67f, 0xa69d },
- { 0xa6a0, 0xa6e5 },
+ { 0xa674, 0xa67b },
+ { 0xa67f, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
- { 0xa7f2, 0xa801 },
- { 0xa803, 0xa805 },
- { 0xa807, 0xa80a },
- { 0xa80c, 0xa822 },
+ { 0xa7d5, 0xa7dc },
+ { 0xa7f2, 0xa805 },
+ { 0xa807, 0xa827 },
{ 0xa840, 0xa873 },
- { 0xa882, 0xa8b3 },
+ { 0xa880, 0xa8c3 },
+ { 0xa8c5, 0xa8c5 },
{ 0xa8f2, 0xa8f7 },
{ 0xa8fb, 0xa8fb },
- { 0xa8fd, 0xa8fe },
- { 0xa90a, 0xa925 },
- { 0xa930, 0xa946 },
+ { 0xa8fd, 0xa8ff },
+ { 0xa90a, 0xa92a },
+ { 0xa930, 0xa952 },
{ 0xa960, 0xa97c },
- { 0xa984, 0xa9b2 },
+ { 0xa980, 0xa9b2 },
+ { 0xa9b4, 0xa9bf },
{ 0xa9cf, 0xa9cf },
- { 0xa9e0, 0xa9e4 },
- { 0xa9e6, 0xa9ef },
+ { 0xa9e0, 0xa9ef },
{ 0xa9fa, 0xa9fe },
- { 0xaa00, 0xaa28 },
- { 0xaa40, 0xaa42 },
- { 0xaa44, 0xaa4b },
+ { 0xaa00, 0xaa36 },
+ { 0xaa40, 0xaa4d },
{ 0xaa60, 0xaa76 },
- { 0xaa7a, 0xaa7a },
- { 0xaa7e, 0xaaaf },
- { 0xaab1, 0xaab1 },
- { 0xaab5, 0xaab6 },
- { 0xaab9, 0xaabd },
+ { 0xaa7a, 0xaabe },
{ 0xaac0, 0xaac0 },
{ 0xaac2, 0xaac2 },
{ 0xaadb, 0xaadd },
- { 0xaae0, 0xaaea },
- { 0xaaf2, 0xaaf4 },
+ { 0xaae0, 0xaaef },
+ { 0xaaf2, 0xaaf5 },
{ 0xab01, 0xab06 },
{ 0xab09, 0xab0e },
{ 0xab11, 0xab16 },
@@ -3113,7 +3262,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xab28, 0xab2e },
{ 0xab30, 0xab5a },
{ 0xab5c, 0xab69 },
- { 0xab70, 0xabe2 },
+ { 0xab70, 0xabea },
{ 0xac00, 0xd7a3 },
{ 0xd7b0, 0xd7c6 },
{ 0xd7cb, 0xd7fb },
@@ -3121,8 +3270,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xfa70, 0xfad9 },
{ 0xfb00, 0xfb06 },
{ 0xfb13, 0xfb17 },
- { 0xfb1d, 0xfb1d },
- { 0xfb1f, 0xfb28 },
+ { 0xfb1d, 0xfb28 },
{ 0xfb2a, 0xfb36 },
{ 0xfb38, 0xfb3c },
{ 0xfb3e, 0xfb3e },
@@ -3149,15 +3297,16 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1003f, 0x1004d },
{ 0x10050, 0x1005d },
{ 0x10080, 0x100fa },
+ { 0x10140, 0x10174 },
{ 0x10280, 0x1029c },
{ 0x102a0, 0x102d0 },
{ 0x10300, 0x1031f },
- { 0x1032d, 0x10340 },
- { 0x10342, 0x10349 },
- { 0x10350, 0x10375 },
+ { 0x1032d, 0x1034a },
+ { 0x10350, 0x1037a },
{ 0x10380, 0x1039d },
{ 0x103a0, 0x103c3 },
{ 0x103c8, 0x103cf },
+ { 0x103d1, 0x103d5 },
{ 0x10400, 0x1049d },
{ 0x104b0, 0x104d3 },
{ 0x104d8, 0x104fb },
@@ -3171,6 +3320,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -3191,8 +3341,9 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x10920, 0x10939 },
{ 0x10980, 0x109b7 },
{ 0x109be, 0x109bf },
- { 0x10a00, 0x10a00 },
- { 0x10a10, 0x10a13 },
+ { 0x10a00, 0x10a03 },
+ { 0x10a05, 0x10a06 },
+ { 0x10a0c, 0x10a13 },
{ 0x10a15, 0x10a17 },
{ 0x10a19, 0x10a35 },
{ 0x10a60, 0x10a7c },
@@ -3206,104 +3357,143 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x10c00, 0x10c48 },
{ 0x10c80, 0x10cb2 },
{ 0x10cc0, 0x10cf2 },
- { 0x10d00, 0x10d23 },
+ { 0x10d00, 0x10d27 },
+ { 0x10d4a, 0x10d65 },
+ { 0x10d69, 0x10d69 },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
+ { 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
+ { 0x10ec2, 0x10ec4 },
+ { 0x10efc, 0x10efc },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
{ 0x10f70, 0x10f81 },
{ 0x10fb0, 0x10fc4 },
{ 0x10fe0, 0x10ff6 },
- { 0x11003, 0x11037 },
- { 0x11071, 0x11072 },
- { 0x11075, 0x11075 },
- { 0x11083, 0x110af },
+ { 0x11000, 0x11045 },
+ { 0x11071, 0x11075 },
+ { 0x11080, 0x110b8 },
+ { 0x110c2, 0x110c2 },
{ 0x110d0, 0x110e8 },
- { 0x11103, 0x11126 },
- { 0x11144, 0x11144 },
- { 0x11147, 0x11147 },
+ { 0x11100, 0x11132 },
+ { 0x11144, 0x11147 },
{ 0x11150, 0x11172 },
{ 0x11176, 0x11176 },
- { 0x11183, 0x111b2 },
+ { 0x11180, 0x111bf },
{ 0x111c1, 0x111c4 },
+ { 0x111ce, 0x111cf },
{ 0x111da, 0x111da },
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
- { 0x11213, 0x1122b },
- { 0x1123f, 0x11240 },
+ { 0x11213, 0x11234 },
+ { 0x11237, 0x11237 },
+ { 0x1123e, 0x11241 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
{ 0x1128f, 0x1129d },
{ 0x1129f, 0x112a8 },
- { 0x112b0, 0x112de },
+ { 0x112b0, 0x112e8 },
+ { 0x11300, 0x11303 },
{ 0x11305, 0x1130c },
{ 0x1130f, 0x11310 },
{ 0x11313, 0x11328 },
{ 0x1132a, 0x11330 },
{ 0x11332, 0x11333 },
{ 0x11335, 0x11339 },
- { 0x1133d, 0x1133d },
+ { 0x1133d, 0x11344 },
+ { 0x11347, 0x11348 },
+ { 0x1134b, 0x1134c },
{ 0x11350, 0x11350 },
- { 0x1135d, 0x11361 },
- { 0x11400, 0x11434 },
+ { 0x11357, 0x11357 },
+ { 0x1135d, 0x11363 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113c0 },
+ { 0x113c2, 0x113c2 },
+ { 0x113c5, 0x113c5 },
+ { 0x113c7, 0x113ca },
+ { 0x113cc, 0x113cd },
+ { 0x113d1, 0x113d1 },
+ { 0x113d3, 0x113d3 },
+ { 0x11400, 0x11441 },
+ { 0x11443, 0x11445 },
{ 0x11447, 0x1144a },
{ 0x1145f, 0x11461 },
- { 0x11480, 0x114af },
+ { 0x11480, 0x114c1 },
{ 0x114c4, 0x114c5 },
{ 0x114c7, 0x114c7 },
- { 0x11580, 0x115ae },
- { 0x115d8, 0x115db },
- { 0x11600, 0x1162f },
+ { 0x11580, 0x115b5 },
+ { 0x115b8, 0x115be },
+ { 0x115d8, 0x115dd },
+ { 0x11600, 0x1163e },
+ { 0x11640, 0x11640 },
{ 0x11644, 0x11644 },
- { 0x11680, 0x116aa },
+ { 0x11680, 0x116b5 },
{ 0x116b8, 0x116b8 },
{ 0x11700, 0x1171a },
+ { 0x1171d, 0x1172a },
{ 0x11740, 0x11746 },
- { 0x11800, 0x1182b },
+ { 0x11800, 0x11838 },
{ 0x118a0, 0x118df },
{ 0x118ff, 0x11906 },
{ 0x11909, 0x11909 },
{ 0x1190c, 0x11913 },
{ 0x11915, 0x11916 },
- { 0x11918, 0x1192f },
- { 0x1193f, 0x1193f },
- { 0x11941, 0x11941 },
+ { 0x11918, 0x11935 },
+ { 0x11937, 0x11938 },
+ { 0x1193b, 0x1193c },
+ { 0x1193f, 0x11942 },
{ 0x119a0, 0x119a7 },
- { 0x119aa, 0x119d0 },
+ { 0x119aa, 0x119d7 },
+ { 0x119da, 0x119df },
{ 0x119e1, 0x119e1 },
- { 0x119e3, 0x119e3 },
- { 0x11a00, 0x11a00 },
- { 0x11a0b, 0x11a32 },
- { 0x11a3a, 0x11a3a },
- { 0x11a50, 0x11a50 },
- { 0x11a5c, 0x11a89 },
+ { 0x119e3, 0x119e4 },
+ { 0x11a00, 0x11a32 },
+ { 0x11a35, 0x11a3e },
+ { 0x11a50, 0x11a97 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
{ 0x11c00, 0x11c08 },
- { 0x11c0a, 0x11c2e },
+ { 0x11c0a, 0x11c36 },
+ { 0x11c38, 0x11c3e },
{ 0x11c40, 0x11c40 },
{ 0x11c72, 0x11c8f },
+ { 0x11c92, 0x11ca7 },
+ { 0x11ca9, 0x11cb6 },
{ 0x11d00, 0x11d06 },
{ 0x11d08, 0x11d09 },
- { 0x11d0b, 0x11d30 },
- { 0x11d46, 0x11d46 },
+ { 0x11d0b, 0x11d36 },
+ { 0x11d3a, 0x11d3a },
+ { 0x11d3c, 0x11d3d },
+ { 0x11d3f, 0x11d41 },
+ { 0x11d43, 0x11d43 },
+ { 0x11d46, 0x11d47 },
{ 0x11d60, 0x11d65 },
{ 0x11d67, 0x11d68 },
- { 0x11d6a, 0x11d89 },
+ { 0x11d6a, 0x11d8e },
+ { 0x11d90, 0x11d91 },
+ { 0x11d93, 0x11d96 },
{ 0x11d98, 0x11d98 },
- { 0x11ee0, 0x11ef2 },
- { 0x11f02, 0x11f02 },
- { 0x11f04, 0x11f10 },
- { 0x11f12, 0x11f33 },
+ { 0x11ee0, 0x11ef6 },
+ { 0x11f00, 0x11f10 },
+ { 0x11f12, 0x11f3a },
+ { 0x11f3e, 0x11f40 },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
+ { 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
{ 0x13000, 0x1342f },
{ 0x13441, 0x13446 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x1612e },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a70, 0x16abe },
@@ -3312,15 +3502,17 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x16b40, 0x16b43 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
- { 0x16f50, 0x16f50 },
- { 0x16f93, 0x16f9f },
+ { 0x16f4f, 0x16f87 },
+ { 0x16f8f, 0x16f9f },
{ 0x16fe0, 0x16fe1 },
{ 0x16fe3, 0x16fe3 },
+ { 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
@@ -3334,6 +3526,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1bc70, 0x1bc7c },
{ 0x1bc80, 0x1bc88 },
{ 0x1bc90, 0x1bc99 },
+ { 0x1bc9e, 0x1bc9e },
{ 0x1d400, 0x1d454 },
{ 0x1d456, 0x1d49c },
{ 0x1d49e, 0x1d49f },
@@ -3366,19 +3559,28 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1d7c4, 0x1d7cb },
{ 0x1df00, 0x1df1e },
{ 0x1df25, 0x1df2a },
+ { 0x1e000, 0x1e006 },
+ { 0x1e008, 0x1e018 },
+ { 0x1e01b, 0x1e021 },
+ { 0x1e023, 0x1e024 },
+ { 0x1e026, 0x1e02a },
{ 0x1e030, 0x1e06d },
+ { 0x1e08f, 0x1e08f },
{ 0x1e100, 0x1e12c },
{ 0x1e137, 0x1e13d },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ad },
{ 0x1e2c0, 0x1e2eb },
{ 0x1e4d0, 0x1e4eb },
+ { 0x1e5d0, 0x1e5ed },
+ { 0x1e5f0, 0x1e5f0 },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
{ 0x1e7f0, 0x1e7fe },
{ 0x1e800, 0x1e8c4 },
{ 0x1e900, 0x1e943 },
+ { 0x1e947, 0x1e947 },
{ 0x1e94b, 0x1e94b },
{ 0x1ee00, 0x1ee03 },
{ 0x1ee05, 0x1ee1f },
@@ -3413,6 +3615,9 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1eea1, 0x1eea3 },
{ 0x1eea5, 0x1eea9 },
{ 0x1eeab, 0x1eebb },
+ { 0x1f130, 0x1f149 },
+ { 0x1f150, 0x1f169 },
+ { 0x1f170, 0x1f189 },
{ 0x20000, 0x2a6df },
{ 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
index 4acb81253f..62ab4e9584 100644
--- a/core/string/char_utils.h
+++ b/core/string/char_utils.h
@@ -38,97 +38,97 @@
#define BSEARCH_CHAR_RANGE(m_array) \
int low = 0; \
int high = sizeof(m_array) / sizeof(m_array[0]) - 1; \
- int middle; \
+ int middle = (low + high) / 2; \
\
while (low <= high) { \
- middle = (low + high) / 2; \
- \
- if (c < m_array[middle].start) { \
+ if (p_char < m_array[middle].start) { \
high = middle - 1; \
- } else if (c > m_array[middle].end) { \
+ } else if (p_char > m_array[middle].end) { \
low = middle + 1; \
} else { \
return true; \
} \
+ \
+ middle = (low + high) / 2; \
} \
\
return false
-static _FORCE_INLINE_ bool is_unicode_identifier_start(char32_t c) {
+constexpr bool is_unicode_identifier_start(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_start);
}
-static _FORCE_INLINE_ bool is_unicode_identifier_continue(char32_t c) {
+constexpr bool is_unicode_identifier_continue(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_continue);
}
-static _FORCE_INLINE_ bool is_unicode_upper_case(char32_t c) {
+constexpr bool is_unicode_upper_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(uppercase_letter);
}
-static _FORCE_INLINE_ bool is_unicode_lower_case(char32_t c) {
+constexpr bool is_unicode_lower_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(lowercase_letter);
}
-static _FORCE_INLINE_ bool is_unicode_letter(char32_t c) {
+constexpr bool is_unicode_letter(char32_t p_char) {
BSEARCH_CHAR_RANGE(unicode_letter);
}
#undef BSEARCH_CHAR_RANGE
-static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
+constexpr bool is_ascii_upper_case(char32_t p_char) {
+ return (p_char >= 'A' && p_char <= 'Z');
}
-static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
+constexpr bool is_ascii_lower_case(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z');
}
-static _FORCE_INLINE_ bool is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
+constexpr bool is_digit(char32_t p_char) {
+ return (p_char >= '0' && p_char <= '9');
}
-static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
- return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+constexpr bool is_hex_digit(char32_t p_char) {
+ return (is_digit(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F'));
}
-static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
- return (c == '0' || c == '1');
+constexpr bool is_binary_digit(char32_t p_char) {
+ return (p_char == '0' || p_char == '1');
}
-static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+constexpr bool is_ascii_alphabet_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z');
}
-static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
+constexpr bool is_ascii_alphanumeric_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9');
}
-static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+constexpr bool is_ascii_identifier_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9') || p_char == '_';
}
-static _FORCE_INLINE_ bool is_symbol(char32_t c) {
- return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
+constexpr bool is_symbol(char32_t p_char) {
+ return p_char != '_' && ((p_char >= '!' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '`') || (p_char >= '{' && p_char <= '~') || p_char == '\t' || p_char == ' ');
}
-static _FORCE_INLINE_ bool is_control(char32_t p_char) {
+constexpr bool is_control(char32_t p_char) {
return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
}
-static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
+constexpr bool is_whitespace(char32_t p_char) {
return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
}
-static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
+constexpr bool is_linebreak(char32_t p_char) {
return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
}
-static _FORCE_INLINE_ bool is_punct(char32_t p_char) {
+constexpr bool is_punct(char32_t p_char) {
return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f);
}
-static _FORCE_INLINE_ bool is_underscore(char32_t p_char) {
+constexpr bool is_underscore(char32_t p_char) {
return (p_char == '_');
}
diff --git a/core/string/fuzzy_search.cpp b/core/string/fuzzy_search.cpp
new file mode 100644
index 0000000000..2fd0d3995e
--- /dev/null
+++ b/core/string/fuzzy_search.cpp
@@ -0,0 +1,349 @@
+/**************************************************************************/
+/* fuzzy_search.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 "fuzzy_search.h"
+
+constexpr float cull_factor = 0.1f;
+constexpr float cull_cutoff = 30.0f;
+const String boundary_chars = "/\\-_.";
+
+static bool _is_valid_interval(const Vector2i &p_interval) {
+ // Empty intervals are represented as (-1, -1).
+ return p_interval.x >= 0 && p_interval.y >= p_interval.x;
+}
+
+static Vector2i _extend_interval(const Vector2i &p_a, const Vector2i &p_b) {
+ if (!_is_valid_interval(p_a)) {
+ return p_b;
+ }
+ if (!_is_valid_interval(p_b)) {
+ return p_a;
+ }
+ return Vector2i(MIN(p_a.x, p_b.x), MAX(p_a.y, p_b.y));
+}
+
+static bool _is_word_boundary(const String &p_str, int p_index) {
+ if (p_index == -1 || p_index == p_str.size()) {
+ return true;
+ }
+ return boundary_chars.find_char(p_str[p_index]) != -1;
+}
+
+bool FuzzySearchToken::try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const {
+ p_match.token_idx = idx;
+ p_match.token_length = string.length();
+ int match_idx = p_target.find(string, p_offset);
+ if (match_idx == -1) {
+ return false;
+ }
+ p_match.add_substring(match_idx, string.length());
+ return true;
+}
+
+bool FuzzySearchToken::try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const {
+ p_match.token_idx = idx;
+ p_match.token_length = string.length();
+ int run_start = -1;
+ int run_len = 0;
+
+ // Search for the subsequence p_token in p_target starting from p_offset, recording each substring for
+ // later scoring and display.
+ for (int i = 0; i < string.length(); i++) {
+ int new_offset = p_target.find_char(string[i], p_offset);
+ if (new_offset < 0) {
+ p_miss_budget--;
+ if (p_miss_budget < 0) {
+ return false;
+ }
+ } else {
+ if (run_start == -1 || p_offset != new_offset) {
+ if (run_start != -1) {
+ p_match.add_substring(run_start, run_len);
+ }
+ run_start = new_offset;
+ run_len = 1;
+ } else {
+ run_len += 1;
+ }
+ p_offset = new_offset + 1;
+ }
+ }
+
+ if (run_start != -1) {
+ p_match.add_substring(run_start, run_len);
+ }
+
+ return true;
+}
+
+void FuzzyTokenMatch::add_substring(int p_substring_start, int p_substring_length) {
+ substrings.append(Vector2i(p_substring_start, p_substring_length));
+ matched_length += p_substring_length;
+ Vector2i substring_interval = { p_substring_start, p_substring_start + p_substring_length - 1 };
+ interval = _extend_interval(interval, substring_interval);
+}
+
+bool FuzzyTokenMatch::intersects(const Vector2i &p_other_interval) const {
+ if (!_is_valid_interval(interval) || !_is_valid_interval(p_other_interval)) {
+ return false;
+ }
+ return interval.y >= p_other_interval.x && interval.x <= p_other_interval.y;
+}
+
+bool FuzzySearchResult::can_add_token_match(const FuzzyTokenMatch &p_match) const {
+ if (p_match.get_miss_count() > miss_budget) {
+ return false;
+ }
+
+ if (p_match.intersects(match_interval)) {
+ if (token_matches.size() == 1) {
+ return false;
+ }
+ for (const FuzzyTokenMatch &existing_match : token_matches) {
+ if (existing_match.intersects(p_match.interval)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool FuzzyTokenMatch::is_case_insensitive(const String &p_original, const String &p_adjusted) const {
+ for (const Vector2i &substr : substrings) {
+ const int end = substr.x + substr.y;
+ for (int i = substr.x; i < end; i++) {
+ if (p_original[i] != p_adjusted[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void FuzzySearchResult::score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const {
+ // This can always be tweaked more. The intuition is that exact matches should almost always
+ // be prioritized over broken up matches, and other criteria more or less act as tie breakers.
+
+ p_match.score = -20 * p_match.get_miss_count() - (p_case_insensitive ? 3 : 0);
+
+ for (const Vector2i &substring : p_match.substrings) {
+ // Score longer substrings higher than short substrings.
+ int substring_score = substring.y * substring.y;
+ // Score matches deeper in path higher than shallower matches
+ if (substring.x > dir_index) {
+ substring_score *= 2;
+ }
+ // Score matches on a word boundary higher than matches within a word
+ if (_is_word_boundary(target, substring.x - 1) || _is_word_boundary(target, substring.x + substring.y)) {
+ substring_score += 4;
+ }
+ // Score exact query matches higher than non-compact subsequence matches
+ if (substring.y == p_match.token_length) {
+ substring_score += 100;
+ }
+ p_match.score += substring_score;
+ }
+}
+
+void FuzzySearchResult::maybe_apply_score_bonus() {
+ // This adds a small bonus to results which match tokens in the same order they appear in the query.
+ int *token_range_starts = (int *)alloca(sizeof(int) * token_matches.size());
+
+ for (const FuzzyTokenMatch &match : token_matches) {
+ token_range_starts[match.token_idx] = match.interval.x;
+ }
+
+ int last = token_range_starts[0];
+ for (int i = 1; i < token_matches.size(); i++) {
+ if (last > token_range_starts[i]) {
+ return;
+ }
+ last = token_range_starts[i];
+ }
+
+ score += 1;
+}
+
+void FuzzySearchResult::add_token_match(const FuzzyTokenMatch &p_match) {
+ score += p_match.score;
+ match_interval = _extend_interval(match_interval, p_match.interval);
+ miss_budget -= p_match.get_miss_count();
+ token_matches.append(p_match);
+}
+
+void remove_low_scores(Vector<FuzzySearchResult> &p_results, float p_cull_score) {
+ // Removes all results with score < p_cull_score in-place.
+ int i = 0;
+ int j = p_results.size() - 1;
+ FuzzySearchResult *results = p_results.ptrw();
+
+ while (true) {
+ // Advances i to an element to remove and j to an element to keep.
+ while (j >= i && results[j].score < p_cull_score) {
+ j--;
+ }
+ while (i < j && results[i].score >= p_cull_score) {
+ i++;
+ }
+ if (i >= j) {
+ break;
+ }
+ results[i++] = results[j--];
+ }
+
+ p_results.resize(j + 1);
+}
+
+void FuzzySearch::sort_and_filter(Vector<FuzzySearchResult> &p_results) const {
+ if (p_results.is_empty()) {
+ return;
+ }
+
+ float avg_score = 0;
+ float max_score = 0;
+
+ for (const FuzzySearchResult &result : p_results) {
+ avg_score += result.score;
+ max_score = MAX(max_score, result.score);
+ }
+
+ // TODO: Tune scoring and culling here to display fewer subsequence soup matches when good matches
+ // are available.
+ avg_score /= p_results.size();
+ float cull_score = MIN(cull_cutoff, Math::lerp(avg_score, max_score, cull_factor));
+ remove_low_scores(p_results, cull_score);
+
+ struct FuzzySearchResultComparator {
+ bool operator()(const FuzzySearchResult &p_lhs, const FuzzySearchResult &p_rhs) const {
+ // Sort on (score, length, alphanumeric) to ensure consistent ordering.
+ if (p_lhs.score == p_rhs.score) {
+ if (p_lhs.target.length() == p_rhs.target.length()) {
+ return p_lhs.target < p_rhs.target;
+ }
+ return p_lhs.target.length() < p_rhs.target.length();
+ }
+ return p_lhs.score > p_rhs.score;
+ }
+ };
+
+ SortArray<FuzzySearchResult, FuzzySearchResultComparator> sorter;
+
+ if (p_results.size() > max_results) {
+ sorter.partial_sort(0, p_results.size(), max_results, p_results.ptrw());
+ p_results.resize(max_results);
+ } else {
+ sorter.sort(p_results.ptrw(), p_results.size());
+ }
+}
+
+void FuzzySearch::set_query(const String &p_query) {
+ tokens.clear();
+ for (const String &string : p_query.split(" ", false)) {
+ tokens.append({ static_cast<int>(tokens.size()), string });
+ }
+
+ case_sensitive = !p_query.is_lowercase();
+
+ struct TokenComparator {
+ bool operator()(const FuzzySearchToken &A, const FuzzySearchToken &B) const {
+ if (A.string.length() == B.string.length()) {
+ return A.idx < B.idx;
+ }
+ return A.string.length() > B.string.length();
+ }
+ };
+
+ // Prioritize matching longer tokens before shorter ones since match overlaps are not accepted.
+ tokens.sort_custom<TokenComparator>();
+}
+
+bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) const {
+ p_result.target = p_target;
+ p_result.dir_index = p_target.rfind_char('/');
+ p_result.miss_budget = max_misses;
+
+ String adjusted_target = case_sensitive ? p_target : p_target.to_lower();
+
+ // For each token, eagerly generate subsequences starting from index 0 and keep the best scoring one
+ // which does not conflict with prior token matches. This is not ensured to find the highest scoring
+ // combination of matches, or necessarily the highest scoring single subsequence, as it only considers
+ // eager subsequences for a given index, and likewise eagerly finds matches for each token in sequence.
+ for (const FuzzySearchToken &token : tokens) {
+ FuzzyTokenMatch best_match;
+ int offset = start_offset;
+
+ while (true) {
+ FuzzyTokenMatch match;
+ if (allow_subsequences) {
+ if (!token.try_fuzzy_match(match, adjusted_target, offset, p_result.miss_budget)) {
+ break;
+ }
+ } else {
+ if (!token.try_exact_match(match, adjusted_target, offset)) {
+ break;
+ }
+ }
+ if (p_result.can_add_token_match(match)) {
+ p_result.score_token_match(match, match.is_case_insensitive(p_target, adjusted_target));
+ if (best_match.token_idx == -1 || best_match.score < match.score) {
+ best_match = match;
+ }
+ }
+ if (_is_valid_interval(match.interval)) {
+ offset = match.interval.x + 1;
+ } else {
+ break;
+ }
+ }
+
+ if (best_match.token_idx == -1) {
+ return false;
+ }
+
+ p_result.add_token_match(best_match);
+ }
+
+ p_result.maybe_apply_score_bonus();
+ return true;
+}
+
+void FuzzySearch::search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const {
+ p_results.clear();
+
+ for (const String &target : p_targets) {
+ FuzzySearchResult result;
+ if (search(target, result)) {
+ p_results.append(result);
+ }
+ }
+
+ sort_and_filter(p_results);
+}
diff --git a/core/string/fuzzy_search.h b/core/string/fuzzy_search.h
new file mode 100644
index 0000000000..5d8ed813c7
--- /dev/null
+++ b/core/string/fuzzy_search.h
@@ -0,0 +1,101 @@
+/**************************************************************************/
+/* fuzzy_search.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 FUZZY_SEARCH_H
+#define FUZZY_SEARCH_H
+
+#include "core/variant/variant.h"
+
+class FuzzyTokenMatch;
+
+struct FuzzySearchToken {
+ int idx = -1;
+ String string;
+
+ bool try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const;
+ bool try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const;
+};
+
+class FuzzyTokenMatch {
+ friend struct FuzzySearchToken;
+ friend class FuzzySearchResult;
+ friend class FuzzySearch;
+
+ int matched_length = 0;
+ int token_length = 0;
+ int token_idx = -1;
+ Vector2i interval = Vector2i(-1, -1); // x and y are both inclusive indices.
+
+ void add_substring(int p_substring_start, int p_substring_length);
+ bool intersects(const Vector2i &p_other_interval) const;
+ bool is_case_insensitive(const String &p_original, const String &p_adjusted) const;
+ int get_miss_count() const { return token_length - matched_length; }
+
+public:
+ int score = 0;
+ Vector<Vector2i> substrings; // x is start index, y is length.
+};
+
+class FuzzySearchResult {
+ friend class FuzzySearch;
+
+ int miss_budget = 0;
+ Vector2i match_interval = Vector2i(-1, -1);
+
+ bool can_add_token_match(const FuzzyTokenMatch &p_match) const;
+ void score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const;
+ void add_token_match(const FuzzyTokenMatch &p_match);
+ void maybe_apply_score_bonus();
+
+public:
+ String target;
+ int score = 0;
+ int dir_index = -1;
+ Vector<FuzzyTokenMatch> token_matches;
+};
+
+class FuzzySearch {
+ Vector<FuzzySearchToken> tokens;
+
+ void sort_and_filter(Vector<FuzzySearchResult> &p_results) const;
+
+public:
+ int start_offset = 0;
+ bool case_sensitive = false;
+ int max_results = 100;
+ int max_misses = 2;
+ bool allow_subsequences = true;
+
+ void set_query(const String &p_query);
+ bool search(const String &p_target, FuzzySearchResult &p_result) const;
+ void search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const;
+};
+
+#endif // FUZZY_SEARCH_H
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index fdc72bc8dc..3faf3bb0c5 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -420,7 +420,7 @@ NodePath::NodePath(const String &p_path) {
continue; // Allow end-of-path :
}
- ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'.");
+ ERR_FAIL_MSG(vformat("Invalid NodePath '%s'.", p_path));
}
subpath.push_back(str);
diff --git a/core/string/translation_domain.cpp b/core/string/translation_domain.cpp
index 53b9ce8379..1ff8dcd752 100644
--- a/core/string/translation_domain.cpp
+++ b/core/string/translation_domain.cpp
@@ -123,7 +123,7 @@ String TranslationDomain::_double_vowels(const String &p_message) const {
}
}
return res;
-};
+}
String TranslationDomain::_replace_with_accented_string(const String &p_message) const {
String res;
@@ -247,7 +247,10 @@ PackedStringArray TranslationDomain::get_loaded_locales() const {
PackedStringArray locales;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
- locales.push_back(E->get_locale());
+ const String &locale = E->get_locale();
+ if (!locales.has(locale)) {
+ locales.push_back(locale);
+ }
}
return locales;
}
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 8e275505b0..da79e472e7 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -246,7 +246,7 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale \"%s\".\nThere should only be one unique translation for a given string under the same context.", String(p_src_text), String(p_context), get_locale()));
map_id_str[p_src_text].set(0, p_xlated_text);
} else {
map_id_str[p_src_text].push_back(p_xlated_text);
@@ -254,12 +254,12 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
}
void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
- ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, vformat("Trying to add plural texts that don't match the required number of plural forms for locale \"%s\".", get_locale()));
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale %s.\nThere should only be one unique translation for a given string under the same context.", p_src_text, p_context, get_locale()));
map_id_str[p_src_text].clear();
}
@@ -280,7 +280,7 @@ StringName TranslationPO::get_message(const StringName &p_src_text, const String
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
return translation_map[p_context][p_src_text][0];
}
@@ -296,7 +296,7 @@ StringName TranslationPO::get_plural_message(const StringName &p_src_text, const
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
int plural_index = _get_plural_index(p_n);
ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 92b473b61f..4f09360ba8 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -118,36 +118,45 @@ void TranslationServer::init_locale_info() {
}
}
-String TranslationServer::standardize_locale(const String &p_locale) const {
- return _standardize_locale(p_locale, false);
+TranslationServer::Locale::operator String() const {
+ String out = language;
+ if (!script.is_empty()) {
+ out = out + "_" + script;
+ }
+ if (!country.is_empty()) {
+ out = out + "_" + country;
+ }
+ if (!variant.is_empty()) {
+ out = out + "_" + variant;
+ }
+ return out;
}
-String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
+TranslationServer::Locale::Locale(const TranslationServer &p_server, const String &p_locale, bool p_add_defaults) {
// Replaces '-' with '_' for macOS style locales.
String univ_locale = p_locale.replace("-", "_");
// Extract locale elements.
- String lang_name, script_name, country_name, variant_name;
Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
- lang_name = locale_elements[0];
+ language = locale_elements[0];
if (locale_elements.size() >= 2) {
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
- script_name = locale_elements[1];
+ script = locale_elements[1];
}
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
- country_name = locale_elements[1];
+ country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
- country_name = locale_elements[2];
- } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
- variant_name = locale_elements[2].to_lower();
+ country = locale_elements[2];
+ } else if (p_server.variant_map.has(locale_elements[2].to_lower()) && p_server.variant_map[locale_elements[2].to_lower()] == language) {
+ variant = locale_elements[2].to_lower();
}
}
if (locale_elements.size() >= 4) {
- if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
- variant_name = locale_elements[3].to_lower();
+ if (p_server.variant_map.has(locale_elements[3].to_lower()) && p_server.variant_map[locale_elements[3].to_lower()] == language) {
+ variant = locale_elements[3].to_lower();
}
}
@@ -155,71 +164,62 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add
Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
for (int i = 0; i < script_extra.size(); i++) {
if (script_extra[i].to_lower() == "cyrillic") {
- script_name = "Cyrl";
+ script = "Cyrl";
break;
} else if (script_extra[i].to_lower() == "latin") {
- script_name = "Latn";
+ script = "Latn";
break;
} else if (script_extra[i].to_lower() == "devanagari") {
- script_name = "Deva";
+ script = "Deva";
break;
- } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
- variant_name = script_extra[i].to_lower();
+ } else if (p_server.variant_map.has(script_extra[i].to_lower()) && p_server.variant_map[script_extra[i].to_lower()] == language) {
+ variant = script_extra[i].to_lower();
}
}
// Handles known non-ISO language names used e.g. on Windows.
- if (locale_rename_map.has(lang_name)) {
- lang_name = locale_rename_map[lang_name];
+ if (p_server.locale_rename_map.has(language)) {
+ language = p_server.locale_rename_map[language];
}
// Handle country renames.
- if (country_rename_map.has(country_name)) {
- country_name = country_rename_map[country_name];
+ if (p_server.country_rename_map.has(country)) {
+ country = p_server.country_rename_map[country];
}
// Remove unsupported script codes.
- if (!script_map.has(script_name)) {
- script_name = "";
+ if (!p_server.script_map.has(script)) {
+ script = "";
}
// Add script code base on language and country codes for some ambiguous cases.
if (p_add_defaults) {
- if (script_name.is_empty()) {
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name) {
- if (country_name.is_empty() || info.supported_countries.has(country_name)) {
- script_name = info.script;
+ if (script.is_empty()) {
+ for (int i = 0; i < p_server.locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = p_server.locale_script_info[i];
+ if (info.name == language) {
+ if (country.is_empty() || info.supported_countries.has(country)) {
+ script = info.script;
break;
}
}
}
}
- if (!script_name.is_empty() && country_name.is_empty()) {
+ if (!script.is_empty() && country.is_empty()) {
// Add conntry code based on script for some ambiguous cases.
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name && info.script == script_name) {
- country_name = info.default_country;
+ for (int i = 0; i < p_server.locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = p_server.locale_script_info[i];
+ if (info.name == language && info.script == script) {
+ country = info.default_country;
break;
}
}
}
}
+}
- // Combine results.
- String out = lang_name;
- if (!script_name.is_empty()) {
- out = out + "_" + script_name;
- }
- if (!country_name.is_empty()) {
- out = out + "_" + country_name;
- }
- if (!variant_name.is_empty()) {
- out = out + "_" + variant_name;
- }
- return out;
+String TranslationServer::standardize_locale(const String &p_locale) const {
+ return Locale(*this, p_locale, false).operator String();
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
@@ -234,8 +234,8 @@ int TranslationServer::compare_locales(const String &p_locale_a, const String &p
return *cached_result;
}
- String locale_a = _standardize_locale(p_locale_a, true);
- String locale_b = _standardize_locale(p_locale_b, true);
+ Locale locale_a = Locale(*this, p_locale_a, true);
+ Locale locale_b = Locale(*this, p_locale_b, true);
if (locale_a == locale_b) {
// Exact match.
@@ -243,26 +243,41 @@ int TranslationServer::compare_locales(const String &p_locale_a, const String &p
return 10;
}
- Vector<String> locale_a_elements = locale_a.split("_");
- Vector<String> locale_b_elements = locale_b.split("_");
- if (locale_a_elements[0] != locale_b_elements[0]) {
+ if (locale_a.language != locale_b.language) {
// No match.
locale_compare_cache.insert(cache_key, 0);
return 0;
}
- // Matching language, both locales have extra parts.
- // Return number of matching elements.
- int matching_elements = 1;
- for (int i = 1; i < locale_a_elements.size(); i++) {
- for (int j = 1; j < locale_b_elements.size(); j++) {
- if (locale_a_elements[i] == locale_b_elements[j]) {
- matching_elements++;
- }
+ // Matching language, both locales have extra parts. Compare the
+ // remaining elements. If both elements are non-empty, check the
+ // match to increase or decrease the score. If either element or
+ // both are empty, leave the score as is.
+ int score = 5;
+ if (!locale_a.script.is_empty() && !locale_b.script.is_empty()) {
+ if (locale_a.script == locale_b.script) {
+ score++;
+ } else {
+ score--;
}
}
- locale_compare_cache.insert(cache_key, matching_elements);
- return matching_elements;
+ if (!locale_a.country.is_empty() && !locale_b.country.is_empty()) {
+ if (locale_a.country == locale_b.country) {
+ score++;
+ } else {
+ score--;
+ }
+ }
+ if (!locale_a.variant.is_empty() && !locale_b.variant.is_empty()) {
+ if (locale_a.variant == locale_b.variant) {
+ score++;
+ } else {
+ score--;
+ }
+ }
+
+ locale_compare_cache.insert(cache_key, score);
+ return score;
}
String TranslationServer::get_locale_name(const String &p_locale) const {
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index 2438349a69..fac41035ae 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -64,6 +64,24 @@ class TranslationServer : public Object {
};
static Vector<LocaleScriptInfo> locale_script_info;
+ struct Locale {
+ String language;
+ String script;
+ String country;
+ String variant;
+
+ bool operator==(const Locale &p_locale) const {
+ return (p_locale.language == language) &&
+ (p_locale.script == script) &&
+ (p_locale.country == country) &&
+ (p_locale.variant == variant);
+ }
+
+ operator String() const;
+
+ Locale(const TranslationServer &p_server, const String &p_locale, bool p_add_defaults);
+ };
+
static HashMap<String, String> language_map;
static HashMap<String, String> script_map;
static HashMap<String, String> locale_rename_map;
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index e6f7492a18..521dfe0b8c 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -33,6 +33,7 @@
#include "core/crypto/crypto_core.h"
#include "core/math/color.h"
#include "core/math/math_funcs.h"
+#include "core/object/object.h"
#include "core/os/memory.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
@@ -1818,7 +1819,7 @@ String String::num(double p_num, int p_decimals) {
#endif
buf[324] = 0;
- //destroy trailing zeroes
+ // Destroy trailing zeroes, except one after period.
{
bool period = false;
int z = 0;
@@ -1835,7 +1836,7 @@ String String::num(double p_num, int p_decimals) {
if (buf[z] == '0') {
buf[z] = 0;
} else if (buf[z] == '.') {
- buf[z] = 0;
+ buf[z + 1] = '0';
break;
} else {
break;
@@ -1850,6 +1851,8 @@ String String::num(double p_num, int p_decimals) {
}
String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
bool sign = p_num < 0;
int64_t n = p_num;
@@ -1888,6 +1891,8 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
}
String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
uint64_t n = p_num;
int chars = 0;
@@ -1924,14 +1929,28 @@ String String::num_real(double p_num, bool p_trailing) {
return num_int64((int64_t)p_num);
}
}
-#ifdef REAL_T_IS_DOUBLE
int decimals = 14;
-#else
+ // We want to align the digits to the above sane default, so we only need
+ // to subtract log10 for numbers with a positive power of ten magnitude.
+ const double abs_num = Math::abs(p_num);
+ if (abs_num > 10) {
+ decimals -= (int)floor(log10(abs_num));
+ }
+ return num(p_num, decimals);
+}
+
+String String::num_real(float p_num, bool p_trailing) {
+ if (p_num == (float)(int64_t)p_num) {
+ if (p_trailing) {
+ return num_int64((int64_t)p_num) + ".0";
+ } else {
+ return num_int64((int64_t)p_num);
+ }
+ }
int decimals = 6;
-#endif
// We want to align the digits to the above sane default, so we only need
// to subtract log10 for numbers with a positive power of ten magnitude.
- double abs_num = Math::abs(p_num);
+ const float abs_num = Math::abs(p_num);
if (abs_num > 10) {
decimals -= (int)floor(log10(abs_num));
}
@@ -3368,7 +3387,7 @@ int String::find(const char *p_str, int p_from) const {
return -1;
}
-int String::find_char(const char32_t &p_char, int p_from) const {
+int String::find_char(char32_t p_char, int p_from) const {
return _cowdata.find(p_char, p_from);
}
@@ -3605,6 +3624,10 @@ int String::rfind(const char *p_str, int p_from) const {
return -1;
}
+int String::rfind_char(char32_t p_char, int p_from) const {
+ return _cowdata.rfind(p_char, p_from);
+}
+
int String::rfindn(const String &p_str, int p_from) const {
// establish a limit
int limit = length() - p_str.length();
@@ -3818,6 +3841,15 @@ bool String::is_quoted() const {
return is_enclosed_in("\"") || is_enclosed_in("'");
}
+bool String::is_lowercase() const {
+ for (const char32_t *str = &operator[](0); *str; str++) {
+ if (is_unicode_upper_case(*str)) {
+ return false;
+ }
+ }
+ return true;
+}
+
int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const {
if (p_string.is_empty()) {
return 0;
@@ -4060,8 +4092,18 @@ String String::format(const Variant &values, const String &placeholder) const {
for (const Variant &key : keys) {
new_string = new_string.replace(placeholder.replace("_", key), d[key]);
}
+ } else if (values.get_type() == Variant::OBJECT) {
+ Object *obj = values.get_validated_object();
+ ERR_FAIL_NULL_V(obj, new_string);
+
+ List<PropertyInfo> props;
+ obj->get_property_list(&props);
+
+ for (const PropertyInfo &E : props) {
+ new_string = new_string.replace(placeholder.replace("_", E.name), obj->get(E.name));
+ }
} else {
- ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data());
+ ERR_PRINT(String("Invalid type: use Array, Dictionary or Object.").ascii().get_data());
}
return new_string;
@@ -4601,7 +4643,7 @@ String String::humanize_size(uint64_t p_size) {
}
if (magnitude == 0) {
- return String::num(p_size) + " " + RTR("B");
+ return String::num_uint64(p_size) + " " + RTR("B");
} else {
String suffix;
switch (magnitude) {
diff --git a/core/string/ustring.h b/core/string/ustring.h
index aa62c9cb18..d6e563223a 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -118,7 +118,7 @@ public:
Char16String &operator+=(char16_t p_char);
int length() const { return size() ? size() - 1 : 0; }
const char16_t *get_data() const;
- operator const char16_t *() const { return get_data(); };
+ operator const char16_t *() const { return get_data(); }
protected:
void copy_from(const char16_t *p_cstr);
@@ -160,7 +160,7 @@ public:
CharString &operator+=(char p_char);
int length() const { return size() ? size() - 1 : 0; }
const char *get_data() const;
- operator const char *() const { return get_data(); };
+ operator const char *() const { return get_data(); }
protected:
void copy_from(const char *p_cstr);
@@ -287,11 +287,12 @@ public:
String substr(int p_from, int p_chars = -1) const;
int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed
int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed
- int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed
+ int find_char(char32_t p_char, int p_from = 0) const; ///< return <0 if failed
int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive
int findn(const char *p_str, int p_from = 0) const; ///< return <0 if failed
int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed
int rfind(const char *p_str, int p_from = -1) const; ///< return <0 if failed
+ int rfind_char(char32_t p_char, int p_from = -1) const; ///< return <0 if failed
int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive
int rfindn(const char *p_str, int p_from = -1) const; ///< return <0 if failed
int findmk(const Vector<String> &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed
@@ -305,6 +306,7 @@ public:
bool is_subsequence_of(const String &p_string) const;
bool is_subsequence_ofn(const String &p_string) const;
bool is_quoted() const;
+ bool is_lowercase() const;
Vector<String> bigrams() const;
float similarity(const String &p_string) const;
String format(const Variant &values, const String &placeholder = "{_}") const;
@@ -332,6 +334,7 @@ public:
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
static String num_real(double p_num, bool p_trailing = true);
+ static String num_real(float p_num, bool p_trailing = true);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(char32_t p_char);
diff --git a/core/templates/a_hash_map.cpp b/core/templates/a_hash_map.cpp
new file mode 100644
index 0000000000..04a14c261a
--- /dev/null
+++ b/core/templates/a_hash_map.cpp
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* a_hash_map.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 "a_hash_map.h"
+#include "core/variant/variant.h"
+
+// Explicit instantiation.
+template class AHashMap<int, int>;
+template class AHashMap<String, int>;
+template class AHashMap<StringName, StringName>;
+template class AHashMap<StringName, Variant>;
+template class AHashMap<StringName, int>;
diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h
new file mode 100644
index 0000000000..29983ea268
--- /dev/null
+++ b/core/templates/a_hash_map.h
@@ -0,0 +1,732 @@
+/**************************************************************************/
+/* a_hash_map.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 A_HASH_MAP_H
+#define A_HASH_MAP_H
+
+#include "core/templates/hash_map.h"
+
+struct HashMapData {
+ union {
+ struct
+ {
+ uint32_t hash;
+ uint32_t hash_to_key;
+ };
+ uint64_t data;
+ };
+};
+
+static_assert(sizeof(HashMapData) == 8);
+
+/**
+ * An array-based implementation of a hash map. It is very efficient in terms of performance and
+ * memory usage. Works like a dynamic array, adding elements to the end of the array, and
+ * allows you to access array elements by their index by using `get_by_index` method.
+ * Example:
+ * ```
+ * AHashMap<int, Object *> map;
+ *
+ * int get_object_id_by_number(int p_number) {
+ * int id = map.get_index(p_number);
+ * return id;
+ * }
+ *
+ * Object *get_object_by_id(int p_id) {
+ * map.get_by_index(p_id).value;
+ * }
+ * ```
+ * Still, don`t erase the elements because ID can break.
+ *
+ * When an element erase, its place is taken by the element from the end.
+ *
+ * <-------------
+ * | |
+ * 6 8 X 9 32 -1 5 -10 7 X X X
+ * 6 8 7 9 32 -1 5 -10 X X X X
+ *
+ *
+ * Use RBMap if you need to iterate over sorted elements.
+ *
+ * Use HashMap if:
+ * - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime.
+ * - You need to preserve the insertion order when using erase.
+ *
+ * It is recommended to use `HashMap` if `KeyValue` size is very large.
+ */
+template <typename TKey, typename TValue,
+ typename Hasher = HashMapHasherDefault,
+ typename Comparator = HashMapComparatorDefault<TKey>>
+class AHashMap {
+public:
+ // Must be a power of two.
+ static constexpr uint32_t INITIAL_CAPACITY = 16;
+ static constexpr uint32_t EMPTY_HASH = 0;
+ static_assert(EMPTY_HASH == 0, "EMPTY_HASH must always be 0 for the memcpy() optimization.");
+
+private:
+ typedef KeyValue<TKey, TValue> MapKeyValue;
+ MapKeyValue *elements = nullptr;
+ HashMapData *map_data = nullptr;
+
+ // Due to optimization, this is `capacity - 1`. Use + 1 to get normal capacity.
+ uint32_t capacity = 0;
+ uint32_t num_elements = 0;
+
+ uint32_t _hash(const TKey &p_key) const {
+ uint32_t hash = Hasher::hash(p_key);
+
+ if (unlikely(hash == EMPTY_HASH)) {
+ hash = EMPTY_HASH + 1;
+ }
+
+ return hash;
+ }
+
+ static _FORCE_INLINE_ uint32_t _get_resize_count(uint32_t p_capacity) {
+ return p_capacity ^ (p_capacity + 1) >> 2; // = get_capacity() * 0.75 - 1; Works only if p_capacity = 2^n - 1.
+ }
+
+ static _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash, uint32_t p_local_capacity) {
+ const uint32_t original_pos = p_hash & p_local_capacity;
+ return (p_pos - original_pos + p_local_capacity + 1) & p_local_capacity;
+ }
+
+ bool _lookup_pos(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos) const {
+ if (unlikely(elements == nullptr)) {
+ return false; // Failed lookups, no elements.
+ }
+ return _lookup_pos_with_hash(p_key, r_pos, r_hash_pos, _hash(p_key));
+ }
+
+ bool _lookup_pos_with_hash(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos, uint32_t p_hash) const {
+ if (unlikely(elements == nullptr)) {
+ return false; // Failed lookups, no elements.
+ }
+
+ uint32_t pos = p_hash & capacity;
+ HashMapData data = map_data[pos];
+ if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+ r_pos = data.hash_to_key;
+ r_hash_pos = pos;
+ return true;
+ }
+
+ if (data.data == EMPTY_HASH) {
+ return false;
+ }
+
+ // A collision occurred.
+ pos = (pos + 1) & capacity;
+ uint32_t distance = 1;
+ while (true) {
+ data = map_data[pos];
+ if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+ r_pos = data.hash_to_key;
+ r_hash_pos = pos;
+ return true;
+ }
+
+ if (data.data == EMPTY_HASH) {
+ return false;
+ }
+
+ if (distance > _get_probe_length(pos, data.hash, capacity)) {
+ return false;
+ }
+
+ pos = (pos + 1) & capacity;
+ distance++;
+ }
+ }
+
+ uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) {
+ uint32_t pos = p_hash & capacity;
+
+ if (map_data[pos].data == EMPTY_HASH) {
+ uint64_t data = ((uint64_t)p_index << 32) | p_hash;
+ map_data[pos].data = data;
+ return pos;
+ }
+
+ uint32_t distance = 1;
+ pos = (pos + 1) & capacity;
+ HashMapData c_data;
+ c_data.hash = p_hash;
+ c_data.hash_to_key = p_index;
+
+ while (true) {
+ if (map_data[pos].data == EMPTY_HASH) {
+#ifdef DEV_ENABLED
+ if (unlikely(distance > 12)) {
+ WARN_PRINT("Excessive collision count (" +
+ itos(distance) + "), is the right hash function being used?");
+ }
+#endif
+ map_data[pos] = c_data;
+ return pos;
+ }
+
+ // Not an empty slot, let's check the probing length of the existing one.
+ uint32_t existing_probe_len = _get_probe_length(pos, map_data[pos].hash, capacity);
+ if (existing_probe_len < distance) {
+ SWAP(c_data, map_data[pos]);
+ distance = existing_probe_len;
+ }
+
+ pos = (pos + 1) & capacity;
+ distance++;
+ }
+ }
+
+ void _resize_and_rehash(uint32_t p_new_capacity) {
+ uint32_t real_old_capacity = capacity + 1;
+ // Capacity can't be 0 and must be 2^n - 1.
+ capacity = MAX(4u, p_new_capacity);
+ uint32_t real_capacity = next_power_of_2(capacity);
+ capacity = real_capacity - 1;
+
+ HashMapData *old_map_data = map_data;
+
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::realloc_static(elements, sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData));
+
+ if (num_elements != 0) {
+ for (uint32_t i = 0; i < real_old_capacity; i++) {
+ HashMapData data = old_map_data[i];
+ if (data.data != EMPTY_HASH) {
+ _insert_with_hash(data.hash, data.hash_to_key);
+ }
+ }
+ }
+
+ Memory::free_static(old_map_data);
+ }
+
+ int32_t _insert_element(const TKey &p_key, const TValue &p_value, uint32_t p_hash) {
+ if (unlikely(elements == nullptr)) {
+ // Allocate on demand to save memory.
+
+ uint32_t real_capacity = capacity + 1;
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData));
+ }
+
+ if (unlikely(num_elements > _get_resize_count(capacity))) {
+ _resize_and_rehash(capacity * 2);
+ }
+
+ memnew_placement(&elements[num_elements], MapKeyValue(p_key, p_value));
+
+ _insert_with_hash(p_hash, num_elements);
+ num_elements++;
+ return num_elements - 1;
+ }
+
+ void _init_from(const AHashMap &p_other) {
+ capacity = p_other.capacity;
+ uint32_t real_capacity = capacity + 1;
+ num_elements = p_other.num_elements;
+
+ if (p_other.num_elements == 0) {
+ return;
+ }
+
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ if constexpr (std::is_trivially_copyable_v<TKey> && std::is_trivially_copyable_v<TValue>) {
+ void *destination = elements;
+ const void *source = p_other.elements;
+ memcpy(destination, source, sizeof(MapKeyValue) * num_elements);
+ } else {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ memnew_placement(&elements[i], MapKeyValue(p_other.elements[i]));
+ }
+ }
+
+ memcpy(map_data, p_other.map_data, sizeof(HashMapData) * real_capacity);
+ }
+
+public:
+ /* Standard Godot Container API */
+
+ _FORCE_INLINE_ uint32_t get_capacity() const { return capacity + 1; }
+ _FORCE_INLINE_ uint32_t size() const { return num_elements; }
+
+ _FORCE_INLINE_ bool is_empty() const {
+ return num_elements == 0;
+ }
+
+ void clear() {
+ if (elements == nullptr || num_elements == 0) {
+ return;
+ }
+
+ memset(map_data, EMPTY_HASH, (capacity + 1) * sizeof(HashMapData));
+ if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ elements[i].key.~TKey();
+ elements[i].value.~TValue();
+ }
+ }
+
+ num_elements = 0;
+ }
+
+ TValue &get(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+ CRASH_COND_MSG(!exists, "AHashMap key not found.");
+ return elements[pos].value;
+ }
+
+ const TValue &get(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+ CRASH_COND_MSG(!exists, "AHashMap key not found.");
+ return elements[pos].value;
+ }
+
+ const TValue *getptr(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+ if (exists) {
+ return &elements[pos].value;
+ }
+ return nullptr;
+ }
+
+ TValue *getptr(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+ if (exists) {
+ return &elements[pos].value;
+ }
+ return nullptr;
+ }
+
+ bool has(const TKey &p_key) const {
+ uint32_t _pos = 0;
+ uint32_t h_pos = 0;
+ return _lookup_pos(p_key, _pos, h_pos);
+ }
+
+ bool erase(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t element_pos = 0;
+ bool exists = _lookup_pos(p_key, element_pos, pos);
+
+ if (!exists) {
+ return false;
+ }
+
+ uint32_t next_pos = (pos + 1) & capacity;
+ while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+ SWAP(map_data[next_pos], map_data[pos]);
+
+ pos = next_pos;
+ next_pos = (next_pos + 1) & capacity;
+ }
+
+ map_data[pos].data = EMPTY_HASH;
+ elements[element_pos].key.~TKey();
+ elements[element_pos].value.~TValue();
+ num_elements--;
+
+ if (element_pos < num_elements) {
+ void *destination = &elements[element_pos];
+ const void *source = &elements[num_elements];
+ memcpy(destination, source, sizeof(MapKeyValue));
+ uint32_t h_pos = 0;
+ _lookup_pos(elements[num_elements].key, pos, h_pos);
+ map_data[h_pos].hash_to_key = element_pos;
+ }
+
+ return true;
+ }
+
+ // Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration.
+ // p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key.
+ bool replace_key(const TKey &p_old_key, const TKey &p_new_key) {
+ if (p_old_key == p_new_key) {
+ return true;
+ }
+ uint32_t pos = 0;
+ uint32_t element_pos = 0;
+ ERR_FAIL_COND_V(_lookup_pos(p_new_key, element_pos, pos), false);
+ ERR_FAIL_COND_V(!_lookup_pos(p_old_key, element_pos, pos), false);
+ MapKeyValue &element = elements[element_pos];
+ const_cast<TKey &>(element.key) = p_new_key;
+
+ uint32_t next_pos = (pos + 1) & capacity;
+ while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+ SWAP(map_data[next_pos], map_data[pos]);
+
+ pos = next_pos;
+ next_pos = (next_pos + 1) & capacity;
+ }
+
+ map_data[pos].data = EMPTY_HASH;
+
+ uint32_t hash = _hash(p_new_key);
+ _insert_with_hash(hash, element_pos);
+
+ return true;
+ }
+
+ // Reserves space for a number of elements, useful to avoid many resizes and rehashes.
+ // If adding a known (possibly large) number of elements at once, must be larger than old capacity.
+ void reserve(uint32_t p_new_capacity) {
+ ERR_FAIL_COND_MSG(p_new_capacity < get_capacity(), "It is impossible to reserve less capacity than is currently available.");
+ if (elements == nullptr) {
+ capacity = MAX(4u, p_new_capacity);
+ capacity = next_power_of_2(capacity) - 1;
+ return; // Unallocated yet.
+ }
+ _resize_and_rehash(p_new_capacity);
+ }
+
+ /** Iterator API **/
+
+ struct ConstIterator {
+ _FORCE_INLINE_ const MapKeyValue &operator*() const {
+ return *pair;
+ }
+ _FORCE_INLINE_ const MapKeyValue *operator->() const {
+ return pair;
+ }
+ _FORCE_INLINE_ ConstIterator &operator++() {
+ pair++;
+ return *this;
+ }
+
+ _FORCE_INLINE_ ConstIterator &operator--() {
+ pair--;
+ if (pair < begin) {
+ pair = end;
+ }
+ return *this;
+ }
+
+ _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return pair == b.pair; }
+ _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return pair != b.pair; }
+
+ _FORCE_INLINE_ explicit operator bool() const {
+ return pair != end;
+ }
+
+ _FORCE_INLINE_ ConstIterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+ pair = p_key;
+ begin = p_begin;
+ end = p_end;
+ }
+ _FORCE_INLINE_ ConstIterator() {}
+ _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+ _FORCE_INLINE_ void operator=(const ConstIterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+
+ private:
+ MapKeyValue *pair = nullptr;
+ MapKeyValue *begin = nullptr;
+ MapKeyValue *end = nullptr;
+ };
+
+ struct Iterator {
+ _FORCE_INLINE_ MapKeyValue &operator*() const {
+ return *pair;
+ }
+ _FORCE_INLINE_ MapKeyValue *operator->() const {
+ return pair;
+ }
+ _FORCE_INLINE_ Iterator &operator++() {
+ pair++;
+ return *this;
+ }
+ _FORCE_INLINE_ Iterator &operator--() {
+ pair--;
+ if (pair < begin) {
+ pair = end;
+ }
+ return *this;
+ }
+
+ _FORCE_INLINE_ bool operator==(const Iterator &b) const { return pair == b.pair; }
+ _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return pair != b.pair; }
+
+ _FORCE_INLINE_ explicit operator bool() const {
+ return pair != end;
+ }
+
+ _FORCE_INLINE_ Iterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+ pair = p_key;
+ begin = p_begin;
+ end = p_end;
+ }
+ _FORCE_INLINE_ Iterator() {}
+ _FORCE_INLINE_ Iterator(const Iterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+ _FORCE_INLINE_ void operator=(const Iterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+
+ operator ConstIterator() const {
+ return ConstIterator(pair, begin, end);
+ }
+
+ private:
+ MapKeyValue *pair = nullptr;
+ MapKeyValue *begin = nullptr;
+ MapKeyValue *end = nullptr;
+ };
+
+ _FORCE_INLINE_ Iterator begin() {
+ return Iterator(elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ Iterator end() {
+ return Iterator(elements + num_elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ Iterator last() {
+ if (unlikely(num_elements == 0)) {
+ return Iterator(nullptr, nullptr, nullptr);
+ }
+ return Iterator(elements + num_elements - 1, elements, elements + num_elements);
+ }
+
+ Iterator find(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return end();
+ }
+ return Iterator(elements + pos, elements, elements + num_elements);
+ }
+
+ void remove(const Iterator &p_iter) {
+ if (p_iter) {
+ erase(p_iter->key);
+ }
+ }
+
+ _FORCE_INLINE_ ConstIterator begin() const {
+ return ConstIterator(elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ ConstIterator end() const {
+ return ConstIterator(elements + num_elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ ConstIterator last() const {
+ if (unlikely(num_elements == 0)) {
+ return ConstIterator(nullptr, nullptr, nullptr);
+ }
+ return ConstIterator(elements + num_elements - 1, elements, elements + num_elements);
+ }
+
+ ConstIterator find(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return end();
+ }
+ return ConstIterator(elements + pos, elements, elements + num_elements);
+ }
+
+ /* Indexing */
+
+ const TValue &operator[](const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ CRASH_COND(!exists);
+ return elements[pos].value;
+ }
+
+ TValue &operator[](const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ uint32_t hash = _hash(p_key);
+ bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+ if (exists) {
+ return elements[pos].value;
+ } else {
+ pos = _insert_element(p_key, TValue(), hash);
+ return elements[pos].value;
+ }
+ }
+
+ /* Insert */
+
+ Iterator insert(const TKey &p_key, const TValue &p_value) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ uint32_t hash = _hash(p_key);
+ bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+ if (!exists) {
+ pos = _insert_element(p_key, p_value, hash);
+ } else {
+ elements[pos].value = p_value;
+ }
+ return Iterator(elements + pos, elements, elements + num_elements);
+ }
+
+ // Inserts an element without checking if it already exists.
+ void insert_new(const TKey &p_key, const TValue &p_value) {
+ DEV_ASSERT(!has(p_key));
+ uint32_t hash = _hash(p_key);
+ _insert_element(p_key, p_value, hash);
+ }
+
+ /* Array methods. */
+
+ // Unsafe. Changing keys and going outside the bounds of an array can lead to undefined behavior.
+ KeyValue<TKey, TValue> *get_elements_ptr() {
+ return elements;
+ }
+
+ // Returns the element index. If not found, returns -1.
+ int get_index(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return -1;
+ }
+ return pos;
+ }
+
+ KeyValue<TKey, TValue> &get_by_index(uint32_t p_index) {
+ CRASH_BAD_UNSIGNED_INDEX(p_index, num_elements);
+ return elements[p_index];
+ }
+
+ bool erase_by_index(uint32_t p_index) {
+ if (p_index >= size()) {
+ return false;
+ }
+ return erase(elements[p_index].key);
+ }
+
+ /* Constructors */
+
+ AHashMap(const AHashMap &p_other) {
+ _init_from(p_other);
+ }
+
+ AHashMap(const HashMap<TKey, TValue> &p_other) {
+ reserve(p_other.size());
+ for (const KeyValue<TKey, TValue> &E : p_other) {
+ uint32_t hash = _hash(E.key);
+ _insert_element(E.key, E.value, hash);
+ }
+ }
+
+ void operator=(const AHashMap &p_other) {
+ if (this == &p_other) {
+ return; // Ignore self assignment.
+ }
+
+ reset();
+
+ _init_from(p_other);
+ }
+
+ void operator=(const HashMap<TKey, TValue> &p_other) {
+ reset();
+ if (p_other.size() > get_capacity()) {
+ reserve(p_other.size());
+ }
+ for (const KeyValue<TKey, TValue> &E : p_other) {
+ uint32_t hash = _hash(E.key);
+ _insert_element(E.key, E.value, hash);
+ }
+ }
+
+ AHashMap(uint32_t p_initial_capacity) {
+ // Capacity can't be 0 and must be 2^n - 1.
+ capacity = MAX(4u, p_initial_capacity);
+ capacity = next_power_of_2(capacity) - 1;
+ }
+ AHashMap() :
+ capacity(INITIAL_CAPACITY - 1) {
+ }
+
+ void reset() {
+ if (elements != nullptr) {
+ if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ elements[i].key.~TKey();
+ elements[i].value.~TValue();
+ }
+ }
+ Memory::free_static(elements);
+ Memory::free_static(map_data);
+ elements = nullptr;
+ }
+ capacity = INITIAL_CAPACITY - 1;
+ num_elements = 0;
+ }
+
+ ~AHashMap() {
+ reset();
+ }
+};
+
+extern template class AHashMap<int, int>;
+extern template class AHashMap<String, int>;
+extern template class AHashMap<StringName, StringName>;
+extern template class AHashMap<StringName, Variant>;
+extern template class AHashMap<StringName, int>;
+
+#endif // A_HASH_MAP_H
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index fedcfaec3b..5f260ee870 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -241,7 +241,7 @@ public:
_FORCE_INLINE_ CowData() {}
_FORCE_INLINE_ ~CowData();
- _FORCE_INLINE_ CowData(CowData<T> &p_from) { _ref(p_from); };
+ _FORCE_INLINE_ CowData(CowData<T> &p_from) { _ref(p_from); }
};
template <typename T>
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index 21eef10297..e681835c5a 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -110,6 +110,16 @@ static _FORCE_INLINE_ uint32_t hash_one_uint64(const uint64_t p_int) {
return uint32_t(v);
}
+static _FORCE_INLINE_ uint64_t hash64_murmur3_64(uint64_t key, uint64_t seed) {
+ key ^= seed;
+ key ^= key >> 33;
+ key *= 0xff51afd7ed558ccd;
+ key ^= key >> 33;
+ key *= 0xc4ceb9fe1a85ec53;
+ key ^= key >> 33;
+ return key;
+}
+
#define HASH_MURMUR3_SEED 0x7F07C65
// Murmurhash3 32-bit version.
// All MurmurHash versions are public domain software, and the author disclaims all copyright to their code.
@@ -393,6 +403,13 @@ struct HashMapHasherDefault {
}
};
+struct HashHasher {
+ static _FORCE_INLINE_ uint32_t hash(const int32_t hash) { return hash; }
+ static _FORCE_INLINE_ uint32_t hash(const uint32_t hash) { return hash; }
+ static _FORCE_INLINE_ uint64_t hash(const int64_t hash) { return hash; }
+ static _FORCE_INLINE_ uint64_t hash(const uint64_t hash) { return hash; }
+};
+
// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed
template <typename T>
struct HashableHasher {
diff --git a/core/templates/list.h b/core/templates/list.h
index 6663f06c30..02afeec74d 100644
--- a/core/templates/list.h
+++ b/core/templates/list.h
@@ -224,7 +224,7 @@ private:
Element *last = nullptr;
int size_cache = 0;
- bool erase(const Element *p_I) {
+ bool erase(Element *p_I) {
ERR_FAIL_NULL_V(p_I, false);
ERR_FAIL_COND_V(p_I->data != this, false);
@@ -244,7 +244,7 @@ private:
p_I->next_ptr->prev_ptr = p_I->prev_ptr;
}
- memdelete_allocator<Element, A>(const_cast<Element *>(p_I));
+ memdelete_allocator<Element, A>(p_I);
size_cache--;
return true;
@@ -430,7 +430,7 @@ public:
/**
* erase an element in the list, by iterator pointing to it. Return true if it was found/erased.
*/
- bool erase(const Element *p_I) {
+ bool erase(Element *p_I) {
if (_data && p_I) {
bool ret = _data->erase(p_I);
diff --git a/core/templates/lru.h b/core/templates/lru.h
index 919c5605aa..7f48c3b2e8 100644
--- a/core/templates/lru.h
+++ b/core/templates/lru.h
@@ -35,9 +35,21 @@
#include "hash_map.h"
#include "list.h"
-template <typename TKey, typename TData, typename Hasher = HashMapHasherDefault, typename Comparator = HashMapComparatorDefault<TKey>>
+#if defined(__GNUC__) && !defined(__clang__)
+#define ADDRESS_DIAGNOSTIC_WARNING_DISABLE \
+ _Pragma("GCC diagnostic push"); \
+ _Pragma("GCC diagnostic ignored \"-Waddress\"");
+
+#define ADDRESS_DIAGNOSTIC_POP \
+ _Pragma("GCC diagnostic pop");
+#else
+#define ADDRESS_DIAGNOSTIC_WARNING_DISABLE
+#define ADDRESS_DIAGNOSTIC_POP
+#endif
+
+template <typename TKey, typename TData, typename Hasher = HashMapHasherDefault, typename Comparator = HashMapComparatorDefault<TKey>, void (*BeforeEvict)(TKey &, TData &) = nullptr>
class LRUCache {
-private:
+public:
struct Pair {
TKey key;
TData data;
@@ -51,16 +63,22 @@ private:
typedef typename List<Pair>::Element *Element;
+private:
List<Pair> _list;
HashMap<TKey, Element, Hasher, Comparator> _map;
size_t capacity;
public:
- const TData *insert(const TKey &p_key, const TData &p_value) {
+ const Pair *insert(const TKey &p_key, const TData &p_value) {
Element *e = _map.getptr(p_key);
Element n = _list.push_front(Pair(p_key, p_value));
if (e) {
+ ADDRESS_DIAGNOSTIC_WARNING_DISABLE;
+ if constexpr (BeforeEvict != nullptr) {
+ BeforeEvict((*e)->get().key, (*e)->get().data);
+ }
+ ADDRESS_DIAGNOSTIC_POP;
_list.erase(*e);
_map.erase(p_key);
}
@@ -68,11 +86,16 @@ public:
while (_map.size() > capacity) {
Element d = _list.back();
+ ADDRESS_DIAGNOSTIC_WARNING_DISABLE
+ if constexpr (BeforeEvict != nullptr) {
+ BeforeEvict(d->get().key, d->get().data);
+ }
+ ADDRESS_DIAGNOSTIC_POP
_map.erase(d->get().key);
_list.pop_back();
}
- return &n->get().data;
+ return &n->get();
}
void clear() {
@@ -84,12 +107,23 @@ public:
return _map.getptr(p_key);
}
+ bool erase(const TKey &p_key) {
+ Element *e = _map.getptr(p_key);
+ if (!e) {
+ return false;
+ }
+ _list.move_to_front(*e);
+ _map.erase(p_key);
+ _list.pop_front();
+ return true;
+ }
+
const TData &get(const TKey &p_key) {
Element *e = _map.getptr(p_key);
CRASH_COND(!e);
_list.move_to_front(*e);
return (*e)->get().data;
- };
+ }
const TData *getptr(const TKey &p_key) {
Element *e = _map.getptr(p_key);
@@ -109,6 +143,11 @@ public:
capacity = p_capacity;
while (_map.size() > capacity) {
Element d = _list.back();
+ ADDRESS_DIAGNOSTIC_WARNING_DISABLE;
+ if constexpr (BeforeEvict != nullptr) {
+ BeforeEvict(d->get().key, d->get().data);
+ }
+ ADDRESS_DIAGNOSTIC_POP;
_map.erase(d->get().key);
_list.pop_back();
}
@@ -124,4 +163,7 @@ public:
}
};
+#undef ADDRESS_DIAGNOSTIC_WARNING_DISABLE
+#undef ADDRESS_DIAGNOSTIC_POP
+
#endif // LRU_H
diff --git a/core/templates/rb_set.h b/core/templates/rb_set.h
index ac7a8df36a..1b69f2f0c2 100644
--- a/core/templates/rb_set.h
+++ b/core/templates/rb_set.h
@@ -76,7 +76,7 @@ public:
}
const T &get() const {
return value;
- };
+ }
Element() {}
};
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 3e62d3dffa..f7a86b8fa3 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -385,7 +385,7 @@ int Array::find_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(ret, "Error calling method from 'find_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(ret, vformat("Error calling method from 'find_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean.");
@@ -445,7 +445,7 @@ int Array::rfind_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(-1, "Error calling method from 'rfind_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(-1, vformat("Error calling method from 'rfind_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean.");
@@ -574,7 +574,7 @@ Array Array::filter(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'filter': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'filter': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -600,7 +600,7 @@ Array Array::map(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'map': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'map': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
new_arr[i] = result;
@@ -626,7 +626,7 @@ Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const
Callable::CallError ce;
p_callable.callp(argptrs, 2, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'reduce': " + Variant::get_callable_error_text(p_callable, argptrs, 2, ce));
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'reduce': %s.", Variant::get_callable_error_text(p_callable, argptrs, 2, ce)));
}
ret = result;
}
@@ -643,7 +643,7 @@ bool Array::any(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'any': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'any': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -665,7 +665,7 @@ bool Array::all(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'all': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'all': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (!(result.operator bool())) {
diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp
index d82aa3583d..afb889551e 100644
--- a/core/variant/callable_bind.cpp
+++ b/core/variant/callable_bind.cpp
@@ -43,7 +43,7 @@ bool CallableCustomBind::_equal_func(const CallableCustom *p_a, const CallableCu
const CallableCustomBind *a = static_cast<const CallableCustomBind *>(p_a);
const CallableCustomBind *b = static_cast<const CallableCustomBind *>(p_b);
- if (!(a->callable != b->callable)) {
+ if (a->callable != b->callable) {
return false;
}
@@ -185,7 +185,7 @@ bool CallableCustomUnbind::_equal_func(const CallableCustom *p_a, const Callable
const CallableCustomUnbind *a = static_cast<const CallableCustomUnbind *>(p_a);
const CallableCustomUnbind *b = static_cast<const CallableCustomUnbind *>(p_b);
- if (!(a->callable != b->callable)) {
+ if (a->callable != b->callable) {
return false;
}
diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h
index 0a23c69cb4..8971fadf73 100644
--- a/core/variant/container_type_validate.h
+++ b/core/variant/container_type_validate.h
@@ -94,7 +94,7 @@ struct ContainerTypeValidate {
return true;
}
- ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(inout_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Attempted to %s a variable of type '%s' into a %s of type '%s'.", String(p_operation), Variant::get_type_name(inout_variant.get_type()), where, Variant::get_type_name(type)));
}
if (type != Variant::OBJECT) {
@@ -113,7 +113,7 @@ struct ContainerTypeValidate {
return true; // This is fine, it's null.
}
Object *object = ObjectDB::get_instance(object_id);
- ERR_FAIL_NULL_V_MSG(object, false, "Attempted to " + String(p_operation) + " an invalid (previously freed?) object instance into a '" + String(where) + ".");
+ ERR_FAIL_NULL_V_MSG(object, false, vformat("Attempted to %s an invalid (previously freed?) object instance into a '%s'.", String(p_operation), String(where)));
#else
Object *object = p_variant;
if (object == nullptr) {
@@ -126,7 +126,7 @@ struct ContainerTypeValidate {
StringName obj_class = object->get_class_name();
if (obj_class != class_name) {
- ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, "Attempted to " + String(p_operation) + " an object of type '" + object->get_class() + "' into a " + where + ", which does not inherit from '" + String(class_name) + "'.");
+ ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, vformat("Attempted to %s an object of type '%s' into a %s, which does not inherit from '%s'.", String(p_operation), object->get_class(), where, String(class_name)));
}
if (script.is_null()) {
@@ -136,8 +136,8 @@ struct ContainerTypeValidate {
Ref<Script> other_script = object->get_script();
// Check base script..
- ERR_FAIL_COND_V_MSG(other_script.is_null(), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
- ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
+ ERR_FAIL_COND_V_MSG(other_script.is_null(), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
+ ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
return true;
}
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index e2865a06be..54936eb8a2 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -951,7 +951,7 @@ bool Variant::is_zero() const {
return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID();
}
case OBJECT: {
- return _get_obj().obj == nullptr;
+ return get_validated_object() == nullptr;
}
case CALLABLE: {
return reinterpret_cast<const Callable *>(_data._mem)->is_null();
@@ -1736,7 +1736,7 @@ String Variant::stringify(int recursion_count) const {
case INT:
return itos(_data._int);
case FLOAT:
- return rtos(_data._float);
+ return String::num_real(_data._float, true);
case STRING:
return *reinterpret_cast<const String *>(_data._mem);
case VECTOR2:
@@ -2719,8 +2719,7 @@ Variant::Variant(const Vector<Plane> &p_array) :
}
}
-Variant::Variant(const Vector<Face3> &p_face_array) :
- type(NIL) {
+Variant::Variant(const Vector<Face3> &p_face_array) {
PackedVector3Array vertices;
int face_count = p_face_array.size();
vertices.resize(face_count * 3);
@@ -2739,8 +2738,7 @@ Variant::Variant(const Vector<Face3> &p_face_array) :
*this = vertices;
}
-Variant::Variant(const Vector<Variant> &p_array) :
- type(NIL) {
+Variant::Variant(const Vector<Variant> &p_array) {
Array arr;
arr.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
@@ -2749,8 +2747,7 @@ Variant::Variant(const Vector<Variant> &p_array) :
*this = arr;
}
-Variant::Variant(const Vector<StringName> &p_array) :
- type(NIL) {
+Variant::Variant(const Vector<StringName> &p_array) {
PackedStringArray v;
int len = p_array.size();
v.resize(len);
@@ -2908,8 +2905,7 @@ Variant::Variant(const IPAddress &p_address) :
memnew_placement(_data._mem, String(p_address));
}
-Variant::Variant(const Variant &p_variant) :
- type(NIL) {
+Variant::Variant(const Variant &p_variant) {
reference(p_variant);
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 3b1924e8ea..9702c67a37 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -814,8 +814,7 @@ public:
static void unregister_types();
Variant(const Variant &p_variant);
- _FORCE_INLINE_ Variant() :
- type(NIL) {}
+ _FORCE_INLINE_ Variant() {}
_FORCE_INLINE_ ~Variant() {
clear();
}
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 6c37d5e4b7..9706457549 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -43,7 +43,7 @@ static LocalVector<VariantConstructData> construct_data[Variant::VARIANT_MAX];
template <typename T>
static void add_constructor(const Vector<String> &arg_names) {
- ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), "Argument names size mismatch for " + Variant::get_type_name(T::get_base_type()) + ".");
+ ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), vformat("Argument names size mismatch for '%s'.", Variant::get_type_name(T::get_base_type())));
VariantConstructData cd;
cd.construct = T::construct;
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index d2c1cde970..ce27fbdf67 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -980,6 +980,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInDictionaryHas<Color>>(Variant::OP_IN, Variant::COLOR, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<StringName>>(Variant::OP_IN, Variant::STRING_NAME, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<NodePath>>(Variant::OP_IN, Variant::NODE_PATH, Variant::DICTIONARY);
+ register_op<OperatorEvaluatorInDictionaryHas<::RID>>(Variant::OP_IN, Variant::RID, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHasObject>(Variant::OP_IN, Variant::OBJECT, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Callable>>(Variant::OP_IN, Variant::CALLABLE, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Signal>>(Variant::OP_IN, Variant::SIGNAL, Variant::DICTIONARY);
@@ -1021,6 +1022,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInArrayFind<Color, Array>>(Variant::OP_IN, Variant::COLOR, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<StringName, Array>>(Variant::OP_IN, Variant::STRING_NAME, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<NodePath, Array>>(Variant::OP_IN, Variant::NODE_PATH, Variant::ARRAY);
+ register_op<OperatorEvaluatorInArrayFind<::RID, Array>>(Variant::OP_IN, Variant::RID, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFindObject>(Variant::OP_IN, Variant::OBJECT, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Callable, Array>>(Variant::OP_IN, Variant::CALLABLE, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Signal, Array>>(Variant::OP_IN, Variant::SIGNAL, Variant::ARRAY);
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 1652f81d99..560067fc08 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -141,6 +141,10 @@ void register_named_setters_getters() {
REGISTER_MEMBER(Color, h);
REGISTER_MEMBER(Color, s);
REGISTER_MEMBER(Color, v);
+
+ REGISTER_MEMBER(Color, ok_hsl_h);
+ REGISTER_MEMBER(Color, ok_hsl_s);
+ REGISTER_MEMBER(Color, ok_hsl_l);
}
void unregister_named_setters_getters() {
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 384fe6c4a6..50932afbb6 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1671,7 +1671,7 @@ static void register_utility_function(const String &p_name, const Vector<String>
bfi.argnames = argnames;
bfi.argcount = T::get_argument_count();
if (!bfi.is_vararg) {
- ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, "wrong number of arguments binding utility function: " + name);
+ ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, vformat("Wrong number of arguments binding utility function: '%s'.", name));
}
bfi.get_arg_type = T::get_argument_type;
bfi.return_type = T::get_return_type();