summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/SCsub128
-rw-r--r--core/config/engine.cpp22
-rw-r--r--core/config/engine.h9
-rw-r--r--core/config/project_settings.cpp55
-rw-r--r--core/config/project_settings.h1
-rw-r--r--core/core_bind.cpp129
-rw-r--r--core/core_bind.h26
-rw-r--r--core/core_builders.py12
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/core_string_names.cpp13
-rw-r--r--core/core_string_names.h13
-rw-r--r--core/crypto/crypto.cpp18
-rw-r--r--core/crypto/crypto.h20
-rw-r--r--core/debugger/debugger_marshalls.cpp8
-rw-r--r--core/debugger/engine_debugger.cpp2
-rw-r--r--core/debugger/remote_debugger.cpp6
-rw-r--r--core/debugger/remote_debugger_peer.cpp4
-rw-r--r--core/doc_data.cpp5
-rw-r--r--core/error/error_macros.cpp51
-rw-r--r--core/error/error_macros.h13
-rw-r--r--core/extension/SCsub2
-rw-r--r--core/extension/extension_api_dump.cpp39
-rw-r--r--core/extension/gdextension.compat.inc49
-rw-r--r--core/extension/gdextension.cpp92
-rw-r--r--core/extension/gdextension.h14
-rw-r--r--core/extension/gdextension_interface.cpp48
-rw-r--r--core/extension/gdextension_interface.h153
-rw-r--r--core/extension/gdextension_manager.cpp3
-rw-r--r--core/extension/make_wrappers.py2
-rw-r--r--core/input/SCsub1
-rw-r--r--core/input/gamecontrollerdb.txt4
-rw-r--r--core/input/godotcontrollerdb.txt2
-rw-r--r--core/input/input.compat.inc41
-rw-r--r--core/input/input.cpp84
-rw-r--r--core/input/input.h22
-rw-r--r--core/input/input_event.cpp12
-rw-r--r--core/input/input_event.h4
-rw-r--r--core/input/input_map.cpp11
-rw-r--r--core/io/dir_access.cpp8
-rw-r--r--core/io/file_access.cpp1
-rw-r--r--core/io/file_access.h1
-rw-r--r--core/io/file_access_compressed.h1
-rw-r--r--core/io/file_access_encrypted.h1
-rw-r--r--core/io/file_access_memory.h1
-rw-r--r--core/io/file_access_pack.cpp4
-rw-r--r--core/io/file_access_pack.h1
-rw-r--r--core/io/file_access_zip.cpp2
-rw-r--r--core/io/file_access_zip.h1
-rw-r--r--core/io/image.cpp220
-rw-r--r--core/io/image.h25
-rw-r--r--core/io/ip.cpp16
-rw-r--r--core/io/ip_address.cpp2
-rw-r--r--core/io/json.cpp4
-rw-r--r--core/io/logger.cpp16
-rw-r--r--core/io/logger.h8
-rw-r--r--core/io/marshalls.cpp164
-rw-r--r--core/io/packed_data_container.cpp2
-rw-r--r--core/io/packet_peer_udp.cpp2
-rw-r--r--core/io/resource.cpp43
-rw-r--r--core/io/resource_format_binary.cpp129
-rw-r--r--core/io/resource_importer.cpp15
-rw-r--r--core/io/resource_loader.cpp268
-rw-r--r--core/io/resource_loader.h12
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/io/stream_peer_tcp.cpp1
-rw-r--r--core/io/udp_server.cpp2
-rw-r--r--core/math/a_star_grid_2d.cpp4
-rw-r--r--core/math/aabb.cpp68
-rw-r--r--core/math/aabb.h13
-rw-r--r--core/math/basis.cpp2
-rw-r--r--core/math/basis.h4
-rw-r--r--core/math/color.cpp25
-rw-r--r--core/math/color.h69
-rw-r--r--core/math/color_names.inc1
-rw-r--r--core/math/delaunay_3d.h83
-rw-r--r--core/math/face3.h2
-rw-r--r--core/math/geometry_2d.cpp111
-rw-r--r--core/math/geometry_2d.h2
-rw-r--r--core/math/math_funcs.h12
-rw-r--r--core/math/plane.h2
-rw-r--r--core/math/projection.h2
-rw-r--r--core/math/quaternion.h2
-rw-r--r--core/math/quick_hull.cpp2
-rw-r--r--core/math/rect2.h4
-rw-r--r--core/math/rect2i.h4
-rw-r--r--core/math/static_raycaster.h2
-rw-r--r--core/math/transform_2d.h2
-rw-r--r--core/math/transform_3d.h2
-rw-r--r--core/math/transform_interpolator.cpp354
-rw-r--r--core/math/transform_interpolator.h51
-rw-r--r--core/math/triangle_mesh.cpp2
-rw-r--r--core/math/vector2.cpp12
-rw-r--r--core/math/vector2.h12
-rw-r--r--core/math/vector2i.cpp12
-rw-r--r--core/math/vector2i.h12
-rw-r--r--core/math/vector3.cpp19
-rw-r--r--core/math/vector3.h13
-rw-r--r--core/math/vector3i.cpp14
-rw-r--r--core/math/vector3i.h12
-rw-r--r--core/math/vector4.cpp27
-rw-r--r--core/math/vector4.h43
-rw-r--r--core/math/vector4i.cpp16
-rw-r--r--core/math/vector4i.h12
-rw-r--r--core/object/class_db.cpp130
-rw-r--r--core/object/class_db.h12
-rw-r--r--core/object/message_queue.cpp66
-rw-r--r--core/object/message_queue.h3
-rw-r--r--core/object/method_bind.h9
-rw-r--r--core/object/object.cpp115
-rw-r--r--core/object/object.h5
-rw-r--r--core/object/script_language.cpp22
-rw-r--r--core/object/script_language.h3
-rw-r--r--core/object/script_language_extension.cpp1
-rw-r--r--core/object/script_language_extension.h13
-rw-r--r--core/object/undo_redo.cpp4
-rw-r--r--core/object/worker_thread_pool.cpp257
-rw-r--r--core/object/worker_thread_pool.h48
-rw-r--r--core/os/main_loop.h1
-rw-r--r--core/os/midi_driver.cpp199
-rw-r--r--core/os/midi_driver.h68
-rw-r--r--core/os/os.cpp14
-rw-r--r--core/os/os.h15
-rw-r--r--core/os/shared_object.h51
-rw-r--r--core/register_core_types.cpp4
-rw-r--r--core/string/char_range.inc663
-rw-r--r--core/string/char_utils.h6
-rw-r--r--core/string/translation.compat.inc5
-rw-r--r--core/string/translation.cpp921
-rw-r--r--core/string/translation.h130
-rw-r--r--core/string/translation_po.cpp90
-rw-r--r--core/string/translation_po.h14
-rw-r--r--core/string/translation_server.compat.inc38
-rw-r--r--core/string/translation_server.cpp947
-rw-r--r--core/string/translation_server.h164
-rw-r--r--core/string/ustring.cpp531
-rw-r--r--core/string/ustring.h17
-rw-r--r--core/templates/command_queue_mt.cpp38
-rw-r--r--core/templates/command_queue_mt.h156
-rw-r--r--core/templates/cowdata.h38
-rw-r--r--core/templates/list.h56
-rw-r--r--core/templates/local_vector.h4
-rw-r--r--core/templates/ring_buffer.h4
-rw-r--r--core/templates/safe_refcount.h2
-rw-r--r--core/templates/simple_type.h21
-rw-r--r--core/typedefs.h7
-rw-r--r--core/variant/array.cpp8
-rw-r--r--core/variant/binder_common.h16
-rw-r--r--core/variant/callable.cpp2
-rw-r--r--core/variant/method_ptrcall.h11
-rw-r--r--core/variant/type_info.h38
-rw-r--r--core/variant/typed_array.h24
-rw-r--r--core/variant/variant.cpp367
-rw-r--r--core/variant/variant.h45
-rw-r--r--core/variant/variant_call.cpp107
-rw-r--r--core/variant/variant_construct.cpp5
-rw-r--r--core/variant/variant_construct.h10
-rw-r--r--core/variant/variant_destruct.cpp1
-rw-r--r--core/variant/variant_destruct.h1
-rw-r--r--core/variant/variant_internal.h37
-rw-r--r--core/variant/variant_op.cpp13
-rw-r--r--core/variant/variant_op.h33
-rw-r--r--core/variant/variant_parser.cpp62
-rw-r--r--core/variant/variant_parser.h4
-rw-r--r--core/variant/variant_setget.cpp53
-rw-r--r--core/variant/variant_setget.h1
-rw-r--r--core/variant/variant_utility.cpp3
166 files changed, 6193 insertions, 2640 deletions
diff --git a/core/SCsub b/core/SCsub
index f7b733a221..1bd4eae16c 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -2,46 +2,13 @@
Import("env")
-import core_builders
-import methods
-
-env.core_sources = []
-
-
-# Generate AES256 script encryption key
import os
-txt = "0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0"
-if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ:
- key = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"]
- ec_valid = True
- if len(key) != 64:
- ec_valid = False
- else:
- txt = ""
- for i in range(len(key) >> 1):
- if i > 0:
- txt += ","
- txts = "0x" + key[i * 2 : i * 2 + 2]
- try:
- int(txts, 16)
- except Exception:
- ec_valid = False
- txt += txts
- if not ec_valid:
- print("Error: Invalid AES256 encryption key, not 64 hexadecimal characters: '" + key + "'.")
- print(
- "Unset 'SCRIPT_AES256_ENCRYPTION_KEY' in your environment "
- "or make sure that it contains exactly 64 hexadecimal characters."
- )
- Exit(255)
-
+import core_builders
-script_encryption_key_contents = (
- '#include "core/config/project_settings.h"\nuint8_t script_encryption_key[32]={' + txt + "};\n"
-)
+import methods
-methods.write_file_if_needed("script_encryption_key.gen.cpp", script_encryption_key_contents)
+env.core_sources = []
# Add required thirdparty code.
@@ -61,7 +28,6 @@ thirdparty_misc_sources = [
# C++ sources
"pcg.cpp",
"polypartition.cpp",
- "clipper.cpp",
"smolv.cpp",
]
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
@@ -194,8 +160,92 @@ env.core_sources += thirdparty_obj
# Godot source files
env.add_source_files(env.core_sources, "*.cpp")
-env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp")
-env.add_source_files(env.core_sources, "version_hash.gen.cpp")
+
+
+# Generate disabled classes
+def disabled_class_builder(target, source, env):
+ with methods.generated_wrapper(target) as file:
+ for c in source[0].read():
+ cs = c.strip()
+ if cs != "":
+ file.write(f"#define ClassDB_Disable_{cs} 1\n")
+
+
+env.CommandNoCache("disabled_classes.gen.h", env.Value(env.disabled_classes), env.Run(disabled_class_builder))
+
+
+# Generate version info
+def version_info_builder(target, source, env):
+ with methods.generated_wrapper(target) as file:
+ file.write(
+ """\
+#define VERSION_SHORT_NAME "{short_name}"
+#define VERSION_NAME "{name}"
+#define VERSION_MAJOR {major}
+#define VERSION_MINOR {minor}
+#define VERSION_PATCH {patch}
+#define VERSION_STATUS "{status}"
+#define VERSION_BUILD "{build}"
+#define VERSION_MODULE_CONFIG "{module_config}"
+#define VERSION_WEBSITE "{website}"
+#define VERSION_DOCS_BRANCH "{docs_branch}"
+#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH
+""".format(**env.version_info)
+ )
+
+
+env.CommandNoCache("version_generated.gen.h", env.Value(env.version_info), env.Run(version_info_builder))
+
+
+# Generate version hash
+def version_hash_builder(target, source, env):
+ with methods.generated_wrapper(target) as file:
+ file.write(
+ """\
+#include "core/version.h"
+
+const char *const VERSION_HASH = "{git_hash}";
+const uint64_t VERSION_TIMESTAMP = {git_timestamp};
+""".format(**env.version_info)
+ )
+
+
+gen_hash = env.CommandNoCache(
+ "version_hash.gen.cpp", env.Value(env.version_info["git_hash"]), env.Run(version_hash_builder)
+)
+env.add_source_files(env.core_sources, gen_hash)
+
+
+# Generate AES256 script encryption key
+def encryption_key_builder(target, source, env):
+ with methods.generated_wrapper(target) as file:
+ file.write(
+ f"""\
+#include "core/config/project_settings.h"
+
+uint8_t script_encryption_key[32] = {{
+ {source[0]}
+}};"""
+ )
+
+
+gdkey = os.environ.get("SCRIPT_AES256_ENCRYPTION_KEY", "0" * 64)
+ec_valid = len(gdkey) == 64
+if ec_valid:
+ try:
+ gdkey = ", ".join([str(int(f"{a}{b}", 16)) for a, b in zip(gdkey[0::2], gdkey[1::2])])
+ except Exception:
+ ec_valid = False
+if not ec_valid:
+ methods.print_error(
+ f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{gdkey}".\n'
+ "Unset `SCRIPT_AES256_ENCRYPTION_KEY` in your environment "
+ "or make sure that it contains exactly 64 hexadecimal characters."
+ )
+ Exit(255)
+gen_encrypt = env.CommandNoCache("script_encryption_key.gen.cpp", env.Value(gdkey), env.Run(encryption_key_builder))
+env.add_source_files(env.core_sources, gen_encrypt)
+
# Certificates
env.Depends(
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 9f4bff3779..3574430cf7 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -82,6 +82,17 @@ int Engine::get_audio_output_latency() const {
return _audio_output_latency;
}
+void Engine::increment_frames_drawn() {
+ if (frame_server_synced) {
+ server_syncs++;
+ } else {
+ server_syncs = 0;
+ }
+ frame_server_synced = false;
+
+ frames_drawn++;
+}
+
uint64_t Engine::get_frames_drawn() {
return frames_drawn;
}
@@ -364,10 +375,21 @@ Engine *Engine::get_singleton() {
return singleton;
}
+bool Engine::notify_frame_server_synced() {
+ frame_server_synced = true;
+ return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
+}
+
Engine::Engine() {
singleton = this;
}
+Engine::~Engine() {
+ if (singleton == this) {
+ singleton = nullptr;
+ }
+}
+
Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
name(p_name),
ptr(p_ptr),
diff --git a/core/config/engine.h b/core/config/engine.h
index d1495b36c2..7e617d8773 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -91,6 +91,10 @@ private:
String write_movie_path;
String shader_cache_path;
+ static constexpr int SERVER_SYNC_FRAME_COUNT_WARNING = 5;
+ int server_syncs = 0;
+ bool frame_server_synced = false;
+
public:
static Engine *get_singleton();
@@ -179,8 +183,11 @@ public:
bool is_generate_spirv_debug_info_enabled() const;
int32_t get_gpu_index() const;
+ void increment_frames_drawn();
+ bool notify_frame_server_synced();
+
Engine();
- virtual ~Engine() {}
+ virtual ~Engine();
};
#endif // ENGINE_H
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 75eea2ef50..5b04986020 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -31,7 +31,6 @@
#include "project_settings.h"
#include "core/core_bind.h" // For Compression enum.
-#include "core/core_string_names.h"
#include "core/input/input_map.h"
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
@@ -253,7 +252,7 @@ bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
}
void ProjectSettings::add_hidden_prefix(const String &p_prefix) {
- ERR_FAIL_COND_MSG(hidden_prefixes.find(p_prefix) > -1, vformat("Hidden prefix '%s' already exists.", p_prefix));
+ ERR_FAIL_COND_MSG(hidden_prefixes.has(p_prefix), vformat("Hidden prefix '%s' already exists.", p_prefix));
hidden_prefixes.push_back(p_prefix);
}
@@ -291,7 +290,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
}
}
} else {
- if (p_name == CoreStringNames::get_singleton()->_custom_features) {
+ if (p_name == CoreStringName(_custom_features)) {
Vector<String> custom_feature_array = String(p_value).split(",");
for (int i = 0; i < custom_feature_array.size(); i++) {
custom_features.insert(custom_feature_array[i]);
@@ -330,9 +329,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
String path = p_value;
if (path.begins_with("*")) {
autoload.is_singleton = true;
- autoload.path = path.substr(1);
+ autoload.path = path.substr(1).simplify_path();
} else {
- autoload.path = path;
+ autoload.path = path.simplify_path();
}
add_autoload(autoload);
} else if (p_name.operator String().begins_with("global_group/")) {
@@ -875,7 +874,7 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
if (!p_custom_features.is_empty()) {
// Store how many properties are saved, add one for custom features, which must always go first.
file->store_32(count + 1);
- String key = CoreStringNames::get_singleton()->_custom_features;
+ String key = CoreStringName(_custom_features);
file->store_pascal_string(key);
int len;
@@ -1017,7 +1016,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
// Check for the existence of a csproj file.
- if (_csproj_exists(get_resource_path())) {
+ if (_csproj_exists(p_path.get_base_dir())) {
// If there is a csproj file, add the C# feature if it doesn't already exist.
if (!project_features.has("C#")) {
project_features.append("C#");
@@ -1473,12 +1472,16 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
- GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);
+#ifdef TOOLS_ENABLED
+ GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false);
+#endif
GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true);
GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres");
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type", PROPERTY_HINT_ENUM, "Stream,Sample"), 0);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type.web", PROPERTY_HINT_ENUM, "Stream,Sample"), 1);
GLOBAL_DEF_RST("audio/general/text_to_speech", false);
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);
@@ -1486,15 +1489,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0);
GLOBAL_DEF("audio/general/ios/mix_with_others", false);
- PackedStringArray extensions;
- extensions.push_back("gd");
- if (Engine::get_singleton()->has_singleton("GodotSharp")) {
- extensions.push_back("cs");
- }
- extensions.push_back("gdshader");
-
- GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions);
-
_add_builtin_input_map();
// Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum.
@@ -1512,6 +1506,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional");
GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "debug/settings/profiler/max_timestamp_query_elements", PROPERTY_HINT_RANGE, "256,65535,1"), 256);
GLOBAL_DEF(PropertyInfo(Variant::BOOL, "compression/formats/zstd/long_distance_matching"), Compression::zstd_long_distance_matching);
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/compression_level", PROPERTY_HINT_RANGE, "1,22,1"), Compression::zstd_level);
@@ -1528,8 +1523,13 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), 0);
+ GLOBAL_DEF_BASIC("internationalization/rendering/root_node_auto_translate", true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
+#ifdef TOOLS_ENABLED
+ GLOBAL_DEF("gui/timers/tooltip_delay_sec.editor_hint", 0.5);
+#endif
GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true);
GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true);
@@ -1550,13 +1550,22 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", 512);
custom_prop_info["rendering/rendering_device/d3d12/max_misc_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", PROPERTY_HINT_RANGE, "32,4096");
- GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version"), 610);
+ // The default value must match the minor part of the Agility SDK version
+ // installed by the scripts provided in the repository
+ // (check `misc/scripts/install_d3d12_sdk_windows.py`).
+ // For example, if the script installs 1.613.3, the default value must be 613.
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version"), 613);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror"), 0);
GLOBAL_DEF("collada/use_ambient", false);
+ // Input settings
+ GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false);
+ GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1);
+
// These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix().
GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray());
GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray());
@@ -1567,6 +1576,14 @@ ProjectSettings::ProjectSettings() {
ProjectSettings::get_singleton()->add_hidden_prefix("input/");
}
+ProjectSettings::ProjectSettings(const String &p_path) {
+ if (load_custom(p_path) == OK) {
+ project_loaded = true;
+ }
+}
+
ProjectSettings::~ProjectSettings() {
- singleton = nullptr;
+ if (singleton == this) {
+ singleton = nullptr;
+ }
}
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index 1bad76acb1..922c88c151 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -224,6 +224,7 @@ public:
#endif
ProjectSettings();
+ ProjectSettings(const String &p_path);
~ProjectSettings();
};
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 8c85030783..a15085bcde 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
#include "core/io/file_access_compressed.h"
#include "core/io/file_access_encrypted.h"
#include "core/io/marshalls.h"
@@ -152,12 +153,10 @@ void ResourceLoader::_bind_methods() {
////// ResourceSaver //////
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, BitField<SaverFlags> p_flags) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
return ::ResourceSaver::save(p_resource, p_path, p_flags);
}
Vector<String> ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), Vector<String>(), "It's not a reference to a valid Resource object.");
List<String> exts;
::ResourceSaver::get_recognized_extensions(p_resource, &exts);
Vector<String> ret;
@@ -195,6 +194,18 @@ void ResourceSaver::_bind_methods() {
////// OS //////
+PackedByteArray OS::get_entropy(int p_bytes) {
+ PackedByteArray pba;
+ pba.resize(p_bytes);
+ Error err = ::OS::get_singleton()->get_entropy(pba.ptrw(), p_bytes);
+ ERR_FAIL_COND_V(err != OK, PackedByteArray());
+ return pba;
+}
+
+String OS::get_system_ca_certificates() {
+ return ::OS::get_singleton()->get_system_ca_certificates();
+}
+
PackedStringArray OS::get_connected_midi_inputs() {
return ::OS::get_singleton()->get_connected_midi_inputs();
}
@@ -338,6 +349,10 @@ bool OS::is_process_running(int p_pid) const {
return ::OS::get_singleton()->is_process_running(p_pid);
}
+int OS::get_process_exit_code(int p_pid) const {
+ return ::OS::get_singleton()->get_process_exit_code(p_pid);
+}
+
int OS::get_process_id() const {
return ::OS::get_singleton()->get_process_id();
}
@@ -570,6 +585,8 @@ String OS::get_unique_id() const {
OS *OS::singleton = nullptr;
void OS::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_entropy", "size"), &OS::get_entropy);
+ ClassDB::bind_method(D_METHOD("get_system_ca_certificates"), &OS::get_system_ca_certificates);
ClassDB::bind_method(D_METHOD("get_connected_midi_inputs"), &OS::get_connected_midi_inputs);
ClassDB::bind_method(D_METHOD("open_midi_inputs"), &OS::open_midi_inputs);
ClassDB::bind_method(D_METHOD("close_midi_inputs"), &OS::close_midi_inputs);
@@ -602,6 +619,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open);
ClassDB::bind_method(D_METHOD("shell_show_in_file_manager", "file_or_dir_path", "open_folder"), &OS::shell_show_in_file_manager, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_process_running", "pid"), &OS::is_process_running);
+ ClassDB::bind_method(D_METHOD("get_process_exit_code", "pid"), &OS::get_process_exit_code);
ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
ClassDB::bind_method(D_METHOD("has_environment", "variable"), &OS::has_environment);
@@ -1422,6 +1440,14 @@ TypedArray<Dictionary> ClassDB::class_get_property_list(const StringName &p_clas
return ret;
}
+StringName ClassDB::class_get_property_getter(const StringName &p_class, const StringName &p_property) {
+ return ::ClassDB::get_property_getter(p_class, p_property);
+}
+
+StringName ClassDB::class_get_property_setter(const StringName &p_class, const StringName &p_property) {
+ return ::ClassDB::get_property_setter(p_class, p_property);
+}
+
Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const {
Variant ret;
::ClassDB::get_property(p_object, p_property, ret);
@@ -1439,6 +1465,15 @@ Error ClassDB::class_set_property(Object *p_object, const StringName &p_property
return OK;
}
+Variant ClassDB::class_get_property_default_value(const StringName &p_class, const StringName &p_property) const {
+ bool valid;
+ Variant ret = ::ClassDB::class_get_default_property_value(p_class, p_property, &valid);
+ if (valid) {
+ return ret;
+ }
+ return Variant();
+}
+
bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const {
return ::ClassDB::has_method(p_class, p_method, p_no_inheritance);
}
@@ -1528,6 +1563,10 @@ StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, c
return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance);
}
+bool ClassDB::is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const {
+ return ::ClassDB::is_enum_bitfield(p_class, p_enum, p_no_inheritance);
+}
+
bool ClassDB::is_class_enabled(const StringName &p_class) const {
return ::ClassDB::is_class_enabled(p_class);
}
@@ -1544,7 +1583,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List
pf == "class_has_method" || pf == "class_get_method_list" ||
pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" ||
pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" ||
- pf == "is_class_enabled");
+ pf == "is_class_enabled" || pf == "is_class_enum_bitfield");
}
if (first_argument_is_class || pf == "is_parent_class") {
for (const String &E : get_class_list()) {
@@ -1570,9 +1609,13 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_property_getter", "class", "property"), &ClassDB::class_get_property_getter);
+ ::ClassDB::bind_method(D_METHOD("class_get_property_setter", "class", "property"), &ClassDB::class_get_property_setter);
::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property);
::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property);
+ ::ClassDB::bind_method(D_METHOD("class_get_property_default_value", "class", "property"), &ClassDB::class_get_property_default_value);
+
::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_method_argument_count", "class", "method", "no_inheritance"), &ClassDB::class_get_method_argument_count, DEFVAL(false));
@@ -1589,6 +1632,8 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::class_get_enum_constants, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::class_get_integer_constant_enum, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false));
+
::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled);
}
@@ -1899,9 +1944,19 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) {
::EngineDebugger::get_singleton()->send_message(p_msg, p_data);
}
+void EngineDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't send debug. No active debugger");
+ ::EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint);
+}
+
+void EngineDebugger::script_debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't send debug. No active debugger");
+ ::EngineDebugger::get_script_debugger()->debug(p_lang, p_can_continue, p_is_error_breakpoint);
+}
+
Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
Callable &capture = *(Callable *)p_user;
- if (capture.is_null()) {
+ if (!capture.is_valid()) {
return FAILED;
}
Variant cmd = p_cmd, data = p_data;
@@ -1915,6 +1970,56 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra
return OK;
}
+void EngineDebugger::line_poll() {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't poll. No active debugger");
+ ::EngineDebugger::get_singleton()->line_poll();
+}
+
+void EngineDebugger::set_lines_left(int p_lines) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set lines left. No active debugger");
+ ::EngineDebugger::get_script_debugger()->set_lines_left(p_lines);
+}
+
+int EngineDebugger::get_lines_left() const {
+ ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get lines left. No active debugger");
+ return ::EngineDebugger::get_script_debugger()->get_lines_left();
+}
+
+void EngineDebugger::set_depth(int p_depth) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set depth. No active debugger");
+ ::EngineDebugger::get_script_debugger()->set_depth(p_depth);
+}
+
+int EngineDebugger::get_depth() const {
+ ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get depth. No active debugger");
+ return ::EngineDebugger::get_script_debugger()->get_depth();
+}
+
+bool EngineDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
+ ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check breakpoint. No active debugger");
+ return ::EngineDebugger::get_script_debugger()->is_breakpoint(p_line, p_source);
+}
+
+bool EngineDebugger::is_skipping_breakpoints() const {
+ ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check skipping breakpoint. No active debugger");
+ return ::EngineDebugger::get_script_debugger()->is_skipping_breakpoints();
+}
+
+void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't insert breakpoint. No active debugger");
+ ::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source);
+}
+
+void EngineDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't remove breakpoint. No active debugger");
+ ::EngineDebugger::get_script_debugger()->remove_breakpoint(p_line, p_source);
+}
+
+void EngineDebugger::clear_breakpoints() {
+ ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't clear breakpoints. No active debugger");
+ ::EngineDebugger::get_script_debugger()->clear_breakpoints();
+}
+
EngineDebugger::~EngineDebugger() {
for (const KeyValue<StringName, Callable> &E : captures) {
::EngineDebugger::unregister_message_capture(E.key);
@@ -1940,7 +2045,23 @@ void EngineDebugger::_bind_methods() {
ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EngineDebugger::unregister_message_capture);
ClassDB::bind_method(D_METHOD("has_capture", "name"), &EngineDebugger::has_capture);
+ ClassDB::bind_method(D_METHOD("line_poll"), &EngineDebugger::line_poll);
+
ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EngineDebugger::send_message);
+ ClassDB::bind_method(D_METHOD("debug", "can_continue", "is_error_breakpoint"), &EngineDebugger::debug, DEFVAL(true), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("script_debug", "language", "can_continue", "is_error_breakpoint"), &EngineDebugger::script_debug, DEFVAL(true), DEFVAL(false));
+
+ ClassDB::bind_method(D_METHOD("set_lines_left", "lines"), &EngineDebugger::set_lines_left);
+ ClassDB::bind_method(D_METHOD("get_lines_left"), &EngineDebugger::get_lines_left);
+
+ ClassDB::bind_method(D_METHOD("set_depth", "depth"), &EngineDebugger::set_depth);
+ ClassDB::bind_method(D_METHOD("get_depth"), &EngineDebugger::get_depth);
+
+ ClassDB::bind_method(D_METHOD("is_breakpoint", "line", "source"), &EngineDebugger::is_breakpoint);
+ ClassDB::bind_method(D_METHOD("is_skipping_breakpoints"), &EngineDebugger::is_skipping_breakpoints);
+ ClassDB::bind_method(D_METHOD("insert_breakpoint", "line", "source"), &EngineDebugger::insert_breakpoint);
+ ClassDB::bind_method(D_METHOD("remove_breakpoint", "line", "source"), &EngineDebugger::remove_breakpoint);
+ ClassDB::bind_method(D_METHOD("clear_breakpoints"), &EngineDebugger::clear_breakpoints);
}
} // namespace core_bind
diff --git a/core/core_bind.h b/core/core_bind.h
index d46321cf5c..69a359e4ed 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -134,6 +134,9 @@ public:
RENDERING_DRIVER_D3D12,
};
+ PackedByteArray get_entropy(int p_bytes);
+ String get_system_ca_certificates();
+
virtual PackedStringArray get_connected_midi_inputs();
virtual void open_midi_inputs();
virtual void close_midi_inputs();
@@ -164,6 +167,7 @@ public:
Error shell_show_in_file_manager(const String &p_path, bool p_open_folder = true);
bool is_process_running(int p_pid) const;
+ int get_process_exit_code(int p_pid) const;
int get_process_id() const;
void set_restart_on_exit(bool p_restart, const Vector<String> &p_restart_arguments = Vector<String>());
@@ -443,9 +447,13 @@ public:
TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const;
+ StringName class_get_property_getter(const StringName &p_class, const StringName &p_property);
+ StringName class_get_property_setter(const StringName &p_class, const StringName &p_property);
Variant class_get_property(Object *p_object, const StringName &p_property) const;
Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const;
+ Variant class_get_property_default_value(const StringName &p_class, const StringName &p_property) const;
+
bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
@@ -461,6 +469,8 @@ public:
PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
+ bool is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
+
bool is_class_enabled(const StringName &p_class) const;
#ifdef TOOLS_ENABLED
@@ -571,9 +581,25 @@ public:
bool has_capture(const StringName &p_name);
void send_message(const String &p_msg, const Array &p_data);
+ void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false);
+ void script_debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);
+ void line_poll();
+
+ void set_lines_left(int p_lines);
+ int get_lines_left() const;
+
+ void set_depth(int p_depth);
+ int get_depth() const;
+
+ bool is_breakpoint(int p_line, const StringName &p_source) const;
+ bool is_skipping_breakpoints() const;
+ void insert_breakpoint(int p_line, const StringName &p_source);
+ void remove_breakpoint(int p_line, const StringName &p_source);
+ void clear_breakpoints();
+
EngineDebugger() { singleton = this; }
~EngineDebugger();
};
diff --git a/core/core_builders.py b/core/core_builders.py
index a401f03693..a3dc935b79 100644
--- a/core/core_builders.py
+++ b/core/core_builders.py
@@ -180,7 +180,7 @@ def make_license_header(target, source, env):
return line
def next_tag(self):
- if not ":" in self.current:
+ if ":" not in self.current:
return ("", [])
tag, line = self.current.split(":", 1)
lines = [line.strip()]
@@ -206,7 +206,7 @@ def make_license_header(target, source, env):
if not tag or not reader.current:
# end of a paragraph start a new part
- if "License" in part and not "Files" in part:
+ if "License" in part and "Files" not in part:
# no Files tag in this one, so assume standalone license
license_list.append(part["License"])
part = {}
@@ -298,13 +298,13 @@ def make_license_header(target, source, env):
f.write("const int LICENSE_COUNT = " + str(len(license_list)) + ";\n")
f.write("const char *const LICENSE_NAMES[] = {\n")
- for l in license_list:
- f.write('\t"' + escape_string(l[0]) + '",\n')
+ for license in license_list:
+ f.write('\t"' + escape_string(license[0]) + '",\n')
f.write("};\n\n")
f.write("const char *const LICENSE_BODIES[] = {\n\n")
- for l in license_list:
- for line in l[1:]:
+ for license in license_list:
+ for line in license[1:]:
if line == ".":
f.write('\t"\\n"\n')
else:
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index aaabbabfd9..5322e39ec0 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -761,6 +761,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR2_ARRAY", Variant::PACKED_VECTOR2_ARRAY);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR3_ARRAY", Variant::PACKED_VECTOR3_ARRAY);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_COLOR_ARRAY", Variant::PACKED_COLOR_ARRAY);
+ BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR4_ARRAY", Variant::PACKED_VECTOR4_ARRAY);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_MAX", Variant::VARIANT_MAX);
//comparison
diff --git a/core/core_string_names.cpp b/core/core_string_names.cpp
index 21645e5efc..9bf625cc15 100644
--- a/core/core_string_names.cpp
+++ b/core/core_string_names.cpp
@@ -33,21 +33,17 @@
CoreStringNames *CoreStringNames::singleton = nullptr;
CoreStringNames::CoreStringNames() :
- _free(StaticCString::create("free")),
+ free_(StaticCString::create("free")),
changed(StaticCString::create("changed")),
- _script(StaticCString::create("script")),
+ script(StaticCString::create("script")),
script_changed(StaticCString::create("script_changed")),
- ___pdcdata(StaticCString::create("___pdcdata")),
- __getvar(StaticCString::create("__getvar")),
_iter_init(StaticCString::create("_iter_init")),
_iter_next(StaticCString::create("_iter_next")),
_iter_get(StaticCString::create("_iter_get")),
get_rid(StaticCString::create("get_rid")),
_to_string(StaticCString::create("_to_string")),
-#ifdef TOOLS_ENABLED
- _sections_unfolded(StaticCString::create("_sections_unfolded")),
-#endif
_custom_features(StaticCString::create("_custom_features")),
+
x(StaticCString::create("x")),
y(StaticCString::create("y")),
z(StaticCString::create("z")),
@@ -70,11 +66,10 @@ CoreStringNames::CoreStringNames() :
g8(StaticCString::create("g8")),
b8(StaticCString::create("b8")),
a8(StaticCString::create("a8")),
+
call(StaticCString::create("call")),
call_deferred(StaticCString::create("call_deferred")),
bind(StaticCString::create("bind")),
- unbind(StaticCString::create("unbind")),
- emit(StaticCString::create("emit")),
notification(StaticCString::create("notification")),
property_list_changed(StaticCString::create("property_list_changed")) {
}
diff --git a/core/core_string_names.h b/core/core_string_names.h
index 1c77cef567..d4ba9110c3 100644
--- a/core/core_string_names.h
+++ b/core/core_string_names.h
@@ -50,20 +50,15 @@ public:
static CoreStringNames *singleton;
- StringName _free;
+ StringName free_; // "free", conflict with C++ keyword.
StringName changed;
- StringName _script;
+ StringName script;
StringName script_changed;
- StringName ___pdcdata;
- StringName __getvar;
StringName _iter_init;
StringName _iter_next;
StringName _iter_get;
StringName get_rid;
StringName _to_string;
-#ifdef TOOLS_ENABLED
- StringName _sections_unfolded;
-#endif
StringName _custom_features;
StringName x;
@@ -92,10 +87,10 @@ public:
StringName call;
StringName call_deferred;
StringName bind;
- StringName unbind;
- StringName emit;
StringName notification;
StringName property_list_changed;
};
+#define CoreStringName(m_name) CoreStringNames::get_singleton()->m_name
+
#endif // CORE_STRING_NAMES_H
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index 7fef819159..d3d0079410 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -72,31 +72,26 @@ void X509Certificate::_bind_methods() {
Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) {
Ref<TLSOptions> opts;
opts.instantiate();
+ opts->mode = MODE_CLIENT;
opts->trusted_ca_chain = p_trusted_chain;
opts->common_name = p_common_name_override;
- opts->verify_mode = TLS_VERIFY_FULL;
return opts;
}
Ref<TLSOptions> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) {
Ref<TLSOptions> opts;
opts.instantiate();
+ opts->mode = MODE_CLIENT_UNSAFE;
opts->trusted_ca_chain = p_trusted_chain;
- if (p_trusted_chain.is_null()) {
- opts->verify_mode = TLS_VERIFY_NONE;
- } else {
- opts->verify_mode = TLS_VERIFY_CERT;
- }
return opts;
}
Ref<TLSOptions> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) {
Ref<TLSOptions> opts;
opts.instantiate();
- opts->server_mode = true;
+ opts->mode = MODE_SERVER;
opts->own_certificate = p_own_certificate;
opts->private_key = p_own_key;
- opts->verify_mode = TLS_VERIFY_NONE;
return opts;
}
@@ -104,6 +99,13 @@ void TLSOptions::_bind_methods() {
ClassDB::bind_static_method("TLSOptions", D_METHOD("client", "trusted_chain", "common_name_override"), &TLSOptions::client, DEFVAL(Ref<X509Certificate>()), DEFVAL(String()));
ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>()));
ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server);
+
+ ClassDB::bind_method(D_METHOD("is_server"), &TLSOptions::is_server);
+ ClassDB::bind_method(D_METHOD("is_unsafe_client"), &TLSOptions::is_unsafe_client);
+ ClassDB::bind_method(D_METHOD("get_common_name_override"), &TLSOptions::get_common_name_override);
+ ClassDB::bind_method(D_METHOD("get_trusted_ca_chain"), &TLSOptions::get_trusted_ca_chain);
+ ClassDB::bind_method(D_METHOD("get_private_key"), &TLSOptions::get_private_key);
+ ClassDB::bind_method(D_METHOD("get_own_certificate"), &TLSOptions::get_own_certificate);
}
/// HMACContext
diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h
index fbd01be86d..16649422cf 100644
--- a/core/crypto/crypto.h
+++ b/core/crypto/crypto.h
@@ -72,17 +72,15 @@ public:
class TLSOptions : public RefCounted {
GDCLASS(TLSOptions, RefCounted);
-public:
- enum TLSVerifyMode {
- TLS_VERIFY_NONE = 0,
- TLS_VERIFY_CERT = 1,
- TLS_VERIFY_FULL = 2,
+private:
+ enum Mode {
+ MODE_CLIENT = 0,
+ MODE_CLIENT_UNSAFE = 1,
+ MODE_SERVER = 2,
};
-private:
- bool server_mode = false;
+ Mode mode = MODE_CLIENT;
String common_name;
- TLSVerifyMode verify_mode = TLS_VERIFY_FULL;
Ref<X509Certificate> trusted_ca_chain;
Ref<X509Certificate> own_certificate;
Ref<CryptoKey> private_key;
@@ -95,12 +93,12 @@ public:
static Ref<TLSOptions> client_unsafe(Ref<X509Certificate> p_trusted_chain);
static Ref<TLSOptions> server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate);
- TLSVerifyMode get_verify_mode() const { return verify_mode; }
- String get_common_name() const { return common_name; }
+ String get_common_name_override() const { return common_name; }
Ref<X509Certificate> get_trusted_ca_chain() const { return trusted_ca_chain; }
Ref<X509Certificate> get_own_certificate() const { return own_certificate; }
Ref<CryptoKey> get_private_key() const { return private_key; }
- bool is_server() const { return server_mode; }
+ bool is_server() const { return mode == MODE_SERVER; }
+ bool is_unsafe_client() const { return mode == MODE_CLIENT_UNSAFE; }
};
class HMACContext : public RefCounted {
diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp
index 3e6b7501c7..f4283e0ea9 100644
--- a/core/debugger/debugger_marshalls.cpp
+++ b/core/debugger/debugger_marshalls.cpp
@@ -38,10 +38,10 @@
Array DebuggerMarshalls::ScriptStackDump::serialize() {
Array arr;
arr.push_back(frames.size() * 3);
- for (int i = 0; i < frames.size(); i++) {
- arr.push_back(frames[i].file);
- arr.push_back(frames[i].line);
- arr.push_back(frames[i].func);
+ for (const ScriptLanguage::StackInfo &frame : frames) {
+ arr.push_back(frame.file);
+ arr.push_back(frame.line);
+ arr.push_back(frame.func);
}
return arr;
}
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
index a7655c874a..97a020e4c3 100644
--- a/core/debugger/engine_debugger.cpp
+++ b/core/debugger/engine_debugger.cpp
@@ -137,7 +137,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
script_debugger = memnew(ScriptDebugger);
// Tell the OS that we want to handle termination signals.
OS::get_singleton()->initialize_debugging();
- } else if (p_uri.find("://") >= 0) {
+ } else if (p_uri.contains("://")) {
const String proto = p_uri.substr(0, p_uri.find("://") + 3);
if (!protocols.has(proto)) {
return;
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index 1973663c72..e2ed7245a2 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -39,6 +39,7 @@
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
+#include "servers/display_server.h"
class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
Object *performance = nullptr;
@@ -206,8 +207,7 @@ void RemoteDebugger::flush_output() {
Vector<String> joined_log_strings;
Vector<String> strings;
Vector<int> types;
- for (int i = 0; i < output_strings.size(); i++) {
- const OutputString &output_string = output_strings[i];
+ for (const OutputString &output_string : output_strings) {
if (output_string.type == MESSAGE_TYPE_ERROR) {
if (!joined_log_strings.is_empty()) {
strings.push_back(String("\n").join(joined_log_strings));
@@ -540,7 +540,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
OS::get_singleton()->delay_usec(10000);
if (Thread::get_caller_id() == Thread::get_main_id()) {
// If this is a busy loop on the main thread, events still need to be processed.
- OS::get_singleton()->process_and_drop_events();
+ DisplayServer::get_singleton()->force_process_and_drop_events();
}
}
}
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 81ee09f515..21a9014626 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -45,7 +45,7 @@ bool RemoteDebuggerPeerTCP::has_message() {
Array RemoteDebuggerPeerTCP::get_message() {
MutexLock lock(mutex);
ERR_FAIL_COND_V(!has_message(), Array());
- Array out = in_queue[0];
+ Array out = in_queue.front()->get();
in_queue.pop_front();
return out;
}
@@ -100,7 +100,7 @@ void RemoteDebuggerPeerTCP::_write_out() {
break; // Nothing left to send
}
mutex.lock();
- Variant var = out_queue[0];
+ Variant var = out_queue.front()->get();
out_queue.pop_front();
mutex.unlock();
int size = 0;
diff --git a/core/doc_data.cpp b/core/doc_data.cpp
index 7549ba884e..672a36c35c 100644
--- a/core/doc_data.cpp
+++ b/core/doc_data.cpp
@@ -152,9 +152,10 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met
return_doc_from_retinfo(p_method, p_methodinfo.return_val);
- for (int i = 0; i < p_methodinfo.arguments.size(); i++) {
+ int i = 0;
+ for (List<PropertyInfo>::ConstIterator itr = p_methodinfo.arguments.begin(); itr != p_methodinfo.arguments.end(); ++itr, ++i) {
DocData::ArgumentDoc argument;
- argument_doc_from_arginfo(argument, p_methodinfo.arguments[i]);
+ argument_doc_from_arginfo(argument, *itr);
int default_arg_index = i - (p_methodinfo.arguments.size() - p_methodinfo.default_arguments.size());
if (default_arg_index >= 0) {
Variant default_arg = p_methodinfo.default_arguments[default_arg_index];
diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp
index 8376c0aaf8..813ee7684f 100644
--- a/core/error/error_macros.cpp
+++ b/core/error/error_macros.cpp
@@ -34,6 +34,12 @@
#include "core/os/os.h"
#include "core/string/ustring.h"
+// Optional physics interpolation warnings try to include the path to the relevant node.
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+#include "core/config/project_settings.h"
+#include "scene/main/node.h"
+#endif
+
static ErrorHandlerList *error_handler_list = nullptr;
void add_error_handler(ErrorHandlerList *p_handler) {
@@ -128,3 +134,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
void _err_flush_stdout() {
fflush(stdout);
}
+
+// Prevent error spam by limiting the warnings to a certain frequency.
+void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) {
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ const uint32_t warn_max = 2048;
+ const uint32_t warn_timeout_seconds = 15;
+
+ static uint32_t warn_count = warn_max;
+ static uint32_t warn_timeout = warn_timeout_seconds;
+
+ uint32_t time_now = UINT32_MAX;
+
+ if (warn_count) {
+ warn_count--;
+ }
+
+ if (!warn_count) {
+ time_now = OS::get_singleton()->get_ticks_msec() / 1000;
+ }
+
+ if ((warn_count == 0) && (time_now >= warn_timeout)) {
+ warn_count = warn_max;
+ warn_timeout = time_now + warn_timeout_seconds;
+
+ if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) {
+ // UINT64_MAX means unused.
+ if (p_id.operator uint64_t() == UINT64_MAX) {
+ _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING);
+ } else {
+ String node_name;
+ if (p_id.is_valid()) {
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+ if (node && node->is_inside_tree()) {
+ node_name = "\"" + String(node->get_path()) + "\"";
+ } else {
+ node_name = "\"unknown\"";
+ }
+ }
+
+ _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING);
+ }
+ }
+ }
+#endif
+}
diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index ab7dbcbd44..d31adb72be 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -31,6 +31,7 @@
#ifndef ERROR_MACROS_H
#define ERROR_MACROS_H
+#include "core/object/object_id.h"
#include "core/typedefs.h"
#include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes.
@@ -71,6 +72,8 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
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();
+void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string);
+
#ifdef __GNUC__
//#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying
#define FUNCTION_STR __FUNCTION__
@@ -832,4 +835,14 @@ void _err_flush_stdout();
#define DEV_CHECK_ONCE(m_cond)
#endif
+/**
+ * Physics Interpolation warnings.
+ * These are spam protection warnings.
+ */
+#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \
+ _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string)
+
+#define PHYSICS_INTERPOLATION_WARNING(m_string) \
+ _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string)
+
#endif // ERROR_MACROS_H
diff --git a/core/extension/SCsub b/core/extension/SCsub
index 901ceec1e8..6ab2d2b0a6 100644
--- a/core/extension/SCsub
+++ b/core/extension/SCsub
@@ -2,8 +2,8 @@
Import("env")
-import make_wrappers
import make_interface_dumper
+import make_wrappers
env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run))
env.CommandNoCache(
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index 69be2d2a8f..848b6f3886 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -189,6 +189,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
{ Variant::PACKED_VECTOR2_ARRAY, ptrsize_32 * 2, ptrsize_64 * 2, ptrsize_32 * 2, ptrsize_64 * 2 },
{ Variant::PACKED_VECTOR3_ARRAY, ptrsize_32 * 2, ptrsize_64 * 2, ptrsize_32 * 2, ptrsize_64 * 2 },
{ Variant::PACKED_COLOR_ARRAY, ptrsize_32 * 2, ptrsize_64 * 2, ptrsize_32 * 2, ptrsize_64 * 2 },
+ { Variant::PACKED_VECTOR4_ARRAY, ptrsize_32 * 2, ptrsize_64 * 2, ptrsize_32 * 2, ptrsize_64 * 2 },
{ Variant::VARIANT_MAX, sizeof(uint64_t) + sizeof(float) * 4, sizeof(uint64_t) + sizeof(float) * 4, sizeof(uint64_t) + sizeof(double) * 4, sizeof(uint64_t) + sizeof(double) * 4 },
};
@@ -230,6 +231,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
static_assert(type_size_array[Variant::PACKED_VECTOR2_ARRAY][sizeof(void *)] == sizeof(PackedVector2Array), "Size of PackedVector2Array mismatch");
static_assert(type_size_array[Variant::PACKED_VECTOR3_ARRAY][sizeof(void *)] == sizeof(PackedVector3Array), "Size of PackedVector3Array mismatch");
static_assert(type_size_array[Variant::PACKED_COLOR_ARRAY][sizeof(void *)] == sizeof(PackedColorArray), "Size of PackedColorArray mismatch");
+ static_assert(type_size_array[Variant::PACKED_VECTOR4_ARRAY][sizeof(void *)] == sizeof(PackedVector4Array), "Size of PackedVector4Array mismatch");
static_assert(type_size_array[Variant::VARIANT_MAX][sizeof(void *)] == sizeof(Variant), "Size of Variant mismatch");
Array core_type_sizes;
@@ -1016,26 +1018,34 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
d2["is_virtual"] = true;
// virtual functions have no hash since no MethodBind is involved
bool has_return = mi.return_val.type != Variant::NIL || (mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
- Array arguments;
- for (int i = (has_return ? -1 : 0); i < mi.arguments.size(); i++) {
- PropertyInfo pinfo = i == -1 ? mi.return_val : mi.arguments[i];
+ if (has_return) {
+ PropertyInfo pinfo = mi.return_val;
Dictionary d3;
- if (i >= 0) {
- d3["name"] = pinfo.name;
+ d3["type"] = get_property_info_type_name(pinfo);
+
+ if (mi.get_argument_meta(-1) > 0) {
+ d3["meta"] = get_type_meta_name((GodotTypeInfo::Metadata)mi.get_argument_meta(-1));
}
+ d2["return_value"] = d3;
+ }
+
+ Array arguments;
+ int i = 0;
+ for (List<PropertyInfo>::ConstIterator itr = mi.arguments.begin(); itr != mi.arguments.end(); ++itr, ++i) {
+ const PropertyInfo &pinfo = *itr;
+ Dictionary d3;
+
+ d3["name"] = pinfo.name;
+
d3["type"] = get_property_info_type_name(pinfo);
if (mi.get_argument_meta(i) > 0) {
d3["meta"] = get_type_meta_name((GodotTypeInfo::Metadata)mi.get_argument_meta(i));
}
- if (i == -1) {
- d2["return_value"] = d3;
- } else {
- arguments.push_back(d3);
- }
+ arguments.push_back(d3);
}
if (arguments.size()) {
@@ -1149,10 +1159,11 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
Array arguments;
- for (int i = 0; i < F.arguments.size(); i++) {
+ int i = 0;
+ for (List<PropertyInfo>::ConstIterator itr = F.arguments.begin(); itr != F.arguments.end(); ++itr, ++i) {
Dictionary d3;
- d3["name"] = F.arguments[i].name;
- d3["type"] = get_property_info_type_name(F.arguments[i]);
+ d3["name"] = itr->name;
+ d3["type"] = get_property_info_type_name(*itr);
if (F.get_argument_meta(i) > 0) {
d3["meta"] = get_type_meta_name((GodotTypeInfo::Metadata)F.get_argument_meta(i));
}
@@ -1190,7 +1201,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
if (F.name.begins_with("_")) {
continue; //hidden property
}
- if (F.name.find("/") >= 0) {
+ if (F.name.contains("/")) {
// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
continue;
}
diff --git a/core/extension/gdextension.compat.inc b/core/extension/gdextension.compat.inc
new file mode 100644
index 0000000000..9dea865dbd
--- /dev/null
+++ b/core/extension/gdextension.compat.inc
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* gdextension.compat.inc */
+/**************************************************************************/
+/* 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 DISABLE_DEPRECATED
+
+Error GDExtension::_open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol) {
+ return ERR_UNAVAILABLE;
+}
+
+void GDExtension::_close_library_bind_compat_88418() {
+}
+
+void GDExtension::_initialize_library_bind_compat_88418(InitializationLevel p_level) {
+}
+
+void GDExtension::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("open_library", "path", "entry_symbol"), &GDExtension::_open_library_bind_compat_88418);
+ ClassDB::bind_compatibility_method(D_METHOD("close_library"), &GDExtension::_close_library_bind_compat_88418);
+ ClassDB::bind_compatibility_method(D_METHOD("initialize_library", "level"), &GDExtension::_initialize_library_bind_compat_88418);
+}
+
+#endif
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 546c40e9cb..cb6832ea39 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -29,6 +29,8 @@
/**************************************************************************/
#include "gdextension.h"
+#include "gdextension.compat.inc"
+
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/object/class_db.h"
@@ -46,6 +48,41 @@ String GDExtension::get_extension_list_config_file() {
return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");
}
+Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
+ Vector<SharedObject> dependencies_shared_objects;
+ if (p_config->has_section("dependencies")) {
+ List<String> config_dependencies;
+ p_config->get_section_keys("dependencies", &config_dependencies);
+
+ for (const String &dependency : config_dependencies) {
+ Vector<String> dependency_tags = dependency.split(".");
+ bool all_tags_met = true;
+ for (int i = 0; i < dependency_tags.size(); i++) {
+ String tag = dependency_tags[i].strip_edges();
+ if (!p_has_feature(tag)) {
+ all_tags_met = false;
+ break;
+ }
+ }
+
+ if (all_tags_met) {
+ Dictionary dependency_value = p_config->get_value("dependencies", dependency);
+ for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
+ String dependency_path = *key;
+ String target_path = dependency_value[*key];
+ if (dependency_path.is_relative_path()) {
+ dependency_path = p_path.get_base_dir().path_join(dependency_path);
+ }
+ dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
+ }
+ break;
+ }
+ }
+ }
+
+ return dependencies_shared_objects;
+}
+
String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
// First, check the explicit libraries.
if (p_config->has_section("libraries")) {
@@ -176,14 +213,14 @@ protected:
if (p_arg < 0) {
return return_value_info.type;
} else {
- return arguments_info[p_arg].type;
+ return arguments_info.get(p_arg).type;
}
}
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
if (p_arg < 0) {
return return_value_info;
} else {
- return arguments_info[p_arg];
+ return arguments_info.get(p_arg);
}
}
@@ -197,7 +234,7 @@ public:
if (p_arg < 0) {
return return_value_metadata;
} else {
- return arguments_metadata[p_arg];
+ return arguments_metadata.get(p_arg);
}
}
#endif
@@ -240,7 +277,7 @@ public:
ret_opaque = r_ret->get_type() == Variant::NIL ? r_ret : VariantInternal::get_opaque_pointer(r_ret);
}
- ptrcall(p_object, argptrs, ret_opaque);
+ ptrcall_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstTypePtr *>(argptrs), (GDExtensionTypePtr)ret_opaque);
if (r_ret && r_ret->get_type() == Variant::OBJECT) {
VariantInternal::update_object_id(r_ret);
@@ -254,7 +291,7 @@ public:
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call GDExtension method bind '%s' on placeholder instance.", name));
#endif
ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug.");
- GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
+ GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance();
ptrcall_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstTypePtr *>(p_args), (GDExtensionTypePtr)r_ret);
}
@@ -284,8 +321,9 @@ public:
return false;
}
- for (uint32_t i = 0; i < p_method_info->argument_count; i++) {
- if (arguments_info[i].type != (Variant::Type)p_method_info->arguments_info[i].type) {
+ List<PropertyInfo>::ConstIterator itr = arguments_info.begin();
+ for (uint32_t i = 0; i < p_method_info->argument_count; ++itr, ++i) {
+ if (itr->type != (Variant::Type)p_method_info->arguments_info[i].type) {
return false;
}
}
@@ -352,7 +390,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->set_func, // GDExtensionClassSet set_func;
p_extension_funcs->get_func, // GDExtensionClassGet get_func;
p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func;
- p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
+ nullptr, // GDExtensionClassFreePropertyList2 free_property_list_func;
p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func;
p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func;
nullptr, // GDExtensionClassValidateProperty validate_property_func;
@@ -371,7 +409,8 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
};
const ClassCreationDeprecatedInfo legacy = {
- p_extension_funcs->notification_func,
+ p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func;
+ p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy);
}
@@ -385,7 +424,7 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->set_func, // GDExtensionClassSet set_func;
p_extension_funcs->get_func, // GDExtensionClassGet get_func;
p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func;
- p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
+ nullptr, // GDExtensionClassFreePropertyList2 free_property_list_func;
p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func;
p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func;
p_extension_funcs->validate_property_func, // GDExtensionClassValidateProperty validate_property_func;
@@ -403,7 +442,11 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->class_userdata, // void *class_userdata;
};
- _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3);
+ const ClassCreationDeprecatedInfo legacy = {
+ nullptr, // GDExtensionClassNotification notification_func;
+ p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
+ };
+ _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy);
}
#endif // DISABLE_DEPRECATED
@@ -479,13 +522,14 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.set = p_extension_funcs->set_func;
extension->gdextension.get = p_extension_funcs->get_func;
extension->gdextension.get_property_list = p_extension_funcs->get_property_list_func;
- extension->gdextension.free_property_list = p_extension_funcs->free_property_list_func;
+ extension->gdextension.free_property_list2 = p_extension_funcs->free_property_list_func;
extension->gdextension.property_can_revert = p_extension_funcs->property_can_revert_func;
extension->gdextension.property_get_revert = p_extension_funcs->property_get_revert_func;
extension->gdextension.validate_property = p_extension_funcs->validate_property_func;
#ifndef DISABLE_DEPRECATED
if (p_deprecated_funcs) {
extension->gdextension.notification = p_deprecated_funcs->notification_func;
+ extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func;
}
#endif // DISABLE_DEPRECATED
extension->gdextension.notification2 = p_extension_funcs->notification_func;
@@ -727,17 +771,24 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String
return *function;
}
-Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) {
+Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) {
String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
- String actual_lib_path;
- Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &actual_lib_path, Engine::get_singleton()->is_editor_hint());
- if (actual_lib_path.get_file() != abs_path.get_file()) {
- // If temporary files are generated, let's change the library path to point at the original,
- // because that's what we want to check to see if it's changed.
- library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file());
+ Vector<String> abs_dependencies_paths;
+ if (p_dependencies != nullptr && !p_dependencies->is_empty()) {
+ for (const SharedObject &dependency : *p_dependencies) {
+ abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
+ }
}
+ OS::GDExtensionData data = {
+ true, // also_set_library_path
+ &library_path, // r_resolved_path
+ Engine::get_singleton()->is_editor_hint(), // generate_temp_files
+ &abs_dependencies_paths, // library_dependencies
+ };
+ Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
+
ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
@@ -970,7 +1021,8 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path,
FileAccess::get_modified_time(library_path));
#endif
- err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol);
+ Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
+ err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies);
if (err != OK) {
// Unreference the extension so that this loading can be considered a failure.
p_extension.unref();
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 1f48edecf7..9393e7399b 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -37,6 +37,7 @@
#include "core/io/config_file.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
+#include "core/os/shared_object.h"
class GDExtensionMethodBind;
@@ -70,6 +71,7 @@ class GDExtension : public Resource {
struct ClassCreationDeprecatedInfo {
#ifndef DISABLE_DEPRECATED
GDExtensionClassNotification notification_func = nullptr;
+ GDExtensionClassFreePropertyList free_property_list_func = nullptr;
#endif // DISABLE_DEPRECATED
};
@@ -123,8 +125,9 @@ public:
static String get_extension_list_config_file();
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
+ static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);
- Error open_library(const String &p_path, const String &p_entry_symbol);
+ Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr);
void close_library();
enum InitializationLevel {
@@ -134,6 +137,15 @@ public:
INITIALIZATION_LEVEL_EDITOR = GDEXTENSION_INITIALIZATION_EDITOR
};
+protected:
+#ifndef DISABLE_DEPRECATED
+ Error _open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol);
+ void _close_library_bind_compat_88418();
+ void _initialize_library_bind_compat_88418(InitializationLevel p_level);
+ static void _bind_compatibility_methods();
+#endif
+
+public:
bool is_library_open() const;
#ifdef TOOLS_ENABLED
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 9b4aa98357..85f83eecfd 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -595,6 +595,8 @@ static GDExtensionVariantFromTypeConstructorFunc gdextension_get_variant_from_ty
return VariantTypeConstructor<PackedVector2Array>::variant_from_type;
case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR3_ARRAY:
return VariantTypeConstructor<PackedVector3Array>::variant_from_type;
+ case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR4_ARRAY:
+ return VariantTypeConstructor<PackedVector4Array>::variant_from_type;
case GDEXTENSION_VARIANT_TYPE_PACKED_COLOR_ARRAY:
return VariantTypeConstructor<PackedColorArray>::variant_from_type;
case GDEXTENSION_VARIANT_TYPE_NIL:
@@ -678,6 +680,8 @@ static GDExtensionTypeFromVariantConstructorFunc gdextension_get_variant_to_type
return VariantTypeConstructor<PackedVector2Array>::type_from_variant;
case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR3_ARRAY:
return VariantTypeConstructor<PackedVector3Array>::type_from_variant;
+ case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR4_ARRAY:
+ return VariantTypeConstructor<PackedVector4Array>::type_from_variant;
case GDEXTENSION_VARIANT_TYPE_PACKED_COLOR_ARRAY:
return VariantTypeConstructor<PackedColorArray>::type_from_variant;
case GDEXTENSION_VARIANT_TYPE_NIL:
@@ -801,12 +805,24 @@ static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionUninitiali
dest->parse_utf8(p_contents, p_size);
}
+static GDExtensionInt gdextension_string_new_with_utf8_chars_and_len2(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
+ return (GDExtensionInt)dest->parse_utf8(p_contents, p_size);
+}
+
static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count) {
memnew_placement(r_dest, String);
String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf16(p_contents, p_char_count);
}
+static GDExtensionInt gdextension_string_new_with_utf16_chars_and_len2(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count, GDExtensionBool p_default_little_endian) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
+ return (GDExtensionInt)dest->parse_utf16(p_contents, p_char_count, p_default_little_endian);
+}
+
static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_char_count) {
memnew_placement(r_dest, String((const char32_t *)p_contents, p_char_count));
}
@@ -958,6 +974,16 @@ static uint64_t gdextension_file_access_get_buffer(GDExtensionConstObjectPtr p_i
return fa->get_buffer(p_dst, p_length);
}
+static uint8_t *gdextension_image_ptrw(GDExtensionObjectPtr p_instance) {
+ Image *img = (Image *)p_instance;
+ return img->ptrw();
+}
+
+static const uint8_t *gdextension_image_ptr(GDExtensionObjectPtr p_instance) {
+ Image *img = (Image *)p_instance;
+ return img->ptr();
+}
+
static int64_t gdextension_worker_thread_pool_add_native_group_task(GDExtensionObjectPtr p_instance, void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description) {
WorkerThreadPool *p = (WorkerThreadPool *)p_instance;
const String *description = (const String *)p_description;
@@ -1116,6 +1142,22 @@ static GDExtensionTypePtr gdextension_packed_vector3_array_operator_index_const(
return (GDExtensionTypePtr)&self->ptr()[p_index];
}
+static GDExtensionTypePtr gdextension_packed_vector4_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
+ PackedVector4Array *self = (PackedVector4Array *)p_self;
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
+ return (GDExtensionTypePtr)&self->ptrw()[p_index];
+}
+
+static GDExtensionTypePtr gdextension_packed_vector4_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
+ const PackedVector4Array *self = (const PackedVector4Array *)p_self;
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
+ return (GDExtensionTypePtr)&self->ptr()[p_index];
+}
+
static GDExtensionVariantPtr gdextension_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
Array *self = (Array *)p_self;
if (unlikely(p_index < 0 || p_index >= self->size())) {
@@ -1578,7 +1620,9 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(string_new_with_wide_chars);
REGISTER_INTERFACE_FUNC(string_new_with_latin1_chars_and_len);
REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars_and_len);
+ REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars_and_len2);
REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars_and_len);
+ REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars_and_len2);
REGISTER_INTERFACE_FUNC(string_new_with_utf32_chars_and_len);
REGISTER_INTERFACE_FUNC(string_new_with_wide_chars_and_len);
REGISTER_INTERFACE_FUNC(string_to_latin1_chars);
@@ -1620,6 +1664,8 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(packed_vector2_array_operator_index_const);
REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index);
REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_vector4_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_vector4_array_operator_index_const);
REGISTER_INTERFACE_FUNC(array_operator_index);
REGISTER_INTERFACE_FUNC(array_operator_index_const);
REGISTER_INTERFACE_FUNC(array_ref);
@@ -1662,6 +1708,8 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(editor_remove_plugin);
REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars);
REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len);
+ REGISTER_INTERFACE_FUNC(image_ptrw);
+ REGISTER_INTERFACE_FUNC(image_ptr);
}
#undef REGISTER_INTERFACE_FUNCTION
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index e9c570e994..fce377f967 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -96,6 +96,7 @@ typedef enum {
GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY,
GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR3_ARRAY,
GDEXTENSION_VARIANT_TYPE_PACKED_COLOR_ARRAY,
+ GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR4_ARRAY,
GDEXTENSION_VARIANT_TYPE_VARIANT_MAX
} GDExtensionVariantType;
@@ -256,6 +257,7 @@ typedef struct {
typedef const GDExtensionPropertyInfo *(*GDExtensionClassGetPropertyList)(GDExtensionClassInstancePtr p_instance, uint32_t *r_count);
typedef void (*GDExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list);
+typedef void (*GDExtensionClassFreePropertyList2)(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list, uint32_t p_count);
typedef GDExtensionBool (*GDExtensionClassPropertyCanRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name);
typedef GDExtensionBool (*GDExtensionClassPropertyGetRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret);
typedef GDExtensionBool (*GDExtensionClassValidateProperty)(GDExtensionClassInstancePtr p_instance, GDExtensionPropertyInfo *p_property);
@@ -333,7 +335,7 @@ typedef struct {
GDExtensionClassSet set_func;
GDExtensionClassGet get_func;
GDExtensionClassGetPropertyList get_property_list_func;
- GDExtensionClassFreePropertyList free_property_list_func;
+ GDExtensionClassFreePropertyList2 free_property_list_func;
GDExtensionClassPropertyCanRevert property_can_revert_func;
GDExtensionClassPropertyGetRevert property_get_revert_func;
GDExtensionClassValidateProperty validate_property_func;
@@ -1580,6 +1582,7 @@ typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUn
/**
* @name string_new_with_utf8_chars_and_len
* @since 4.1
+ * @deprecated in Godot 4.3. Use `string_new_with_utf8_chars_and_len2` instead.
*
* Creates a String from a UTF-8 encoded C string with the given length.
*
@@ -1590,8 +1593,23 @@ typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUn
typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
/**
+ * @name string_new_with_utf8_chars_and_len2
+ * @since 4.3
+ *
+ * Creates a String from a UTF-8 encoded C string with the given length.
+ *
+ * @param r_dest A pointer to a Variant to hold the newly created String.
+ * @param p_contents A pointer to a UTF-8 encoded C string.
+ * @param p_size The number of bytes (not code units).
+ *
+ * @return Error code signifying if the operation successful.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen2)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
+
+/**
* @name string_new_with_utf16_chars_and_len
* @since 4.1
+ * @deprecated in Godot 4.3. Use `string_new_with_utf16_chars_and_len2` instead.
*
* Creates a String from a UTF-16 encoded C string with the given length.
*
@@ -1602,6 +1620,21 @@ typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUnin
typedef void (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count);
/**
+ * @name string_new_with_utf16_chars_and_len2
+ * @since 4.3
+ *
+ * Creates a String from a UTF-16 encoded C string with the given length.
+ *
+ * @param r_dest A pointer to a Variant to hold the newly created String.
+ * @param p_contents A pointer to a UTF-16 encoded C string.
+ * @param p_size The number of characters (not bytes).
+ * @param p_default_little_endian If true, UTF-16 use little endian.
+ *
+ * @return Error code signifying if the operation successful.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen2)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count, GDExtensionBool p_default_little_endian);
+
+/**
* @name string_new_with_utf32_chars_and_len
* @since 4.1
*
@@ -1897,6 +1930,36 @@ typedef void (*GDExtensionInterfaceFileAccessStoreBuffer)(GDExtensionObjectPtr p
*/
typedef uint64_t (*GDExtensionInterfaceFileAccessGetBuffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length);
+/* INTERFACE: Image Utilities */
+
+/**
+ * @name image_ptrw
+ * @since 4.3
+ *
+ * Returns writable pointer to internal Image buffer.
+ *
+ * @param p_instance A pointer to a Image object.
+ *
+ * @return Pointer to internal Image buffer.
+ *
+ * @see Image::ptrw()
+ */
+typedef uint8_t *(*GDExtensionInterfaceImagePtrw)(GDExtensionObjectPtr p_instance);
+
+/**
+ * @name image_ptr
+ * @since 4.3
+ *
+ * Returns read only pointer to internal Image buffer.
+ *
+ * @param p_instance A pointer to a Image object.
+ *
+ * @return Pointer to internal Image buffer.
+ *
+ * @see Image::ptr()
+ */
+typedef const uint8_t *(*GDExtensionInterfaceImagePtr)(GDExtensionObjectPtr p_instance);
+
/* INTERFACE: WorkerThreadPool Utilities */
/**
@@ -1963,32 +2026,6 @@ typedef uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndex)(GDExtension
typedef const uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
/**
- * @name packed_color_array_operator_index
- * @since 4.1
- *
- * Gets a pointer to a color in a PackedColorArray.
- *
- * @param p_self A pointer to a PackedColorArray object.
- * @param p_index The index of the Color to get.
- *
- * @return A pointer to the requested Color.
- */
-typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
-
-/**
- * @name packed_color_array_operator_index_const
- * @since 4.1
- *
- * Gets a const pointer to a color in a PackedColorArray.
- *
- * @param p_self A const pointer to a const PackedColorArray object.
- * @param p_index The index of the Color to get.
- *
- * @return A const pointer to the requested Color.
- */
-typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
-
-/**
* @name packed_float32_array_operator_index
* @since 4.1
*
@@ -2171,6 +2208,58 @@ typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndex
typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
/**
+ * @name packed_vector4_array_operator_index
+ * @since 4.3
+ *
+ * Gets a pointer to a Vector4 in a PackedVector4Array.
+ *
+ * @param p_self A pointer to a PackedVector4Array object.
+ * @param p_index The index of the Vector4 to get.
+ *
+ * @return A pointer to the requested Vector4.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector4ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_vector4_array_operator_index_const
+ * @since 4.3
+ *
+ * Gets a const pointer to a Vector4 in a PackedVector4Array.
+ *
+ * @param p_self A const pointer to a PackedVector4Array object.
+ * @param p_index The index of the Vector4 to get.
+ *
+ * @return A const pointer to the requested Vector4.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector4ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_color_array_operator_index
+ * @since 4.1
+ *
+ * Gets a pointer to a color in a PackedColorArray.
+ *
+ * @param p_self A pointer to a PackedColorArray object.
+ * @param p_index The index of the Color to get.
+ *
+ * @return A pointer to the requested Color.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_color_array_operator_index_const
+ * @since 4.1
+ *
+ * Gets a const pointer to a color in a PackedColorArray.
+ *
+ * @param p_self A const pointer to a PackedColorArray object.
+ * @param p_index The index of the Color to get.
+ *
+ * @return A const pointer to the requested Color.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
* @name array_operator_index
* @since 4.1
*
@@ -2711,12 +2800,16 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(G
*
* Registers an integer constant on an extension class in the ClassDB.
*
+ * Note about registering bitfield values (if p_is_bitfield is true): even though p_constant_value is signed, language bindings are
+ * advised to treat bitfields as uint64_t, since this is generally clearer and can prevent mistakes like using -1 for setting all bits.
+ * Language APIs should thus provide an abstraction that registers bitfields (uint64_t) separately from regular constants (int64_t).
+ *
* @param p_library A pointer the library received by the GDExtension's entry point function.
* @param p_class_name A pointer to a StringName with the class name.
* @param p_enum_name A pointer to a StringName with the enum name.
* @param p_constant_name A pointer to a StringName with the constant name.
* @param p_constant_value The constant value.
- * @param p_is_bitfield Whether or not this is a bit field.
+ * @param p_is_bitfield Whether or not this constant is part of a bitfield.
*/
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield);
@@ -2847,7 +2940,7 @@ typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNam
*
* The provided pointer can be immediately freed once the function returns.
*
- * @param p_data A pointer to an UTF-8 encoded C string (null terminated).
+ * @param p_data A pointer to a UTF-8 encoded C string (null terminated).
*/
typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *p_data);
@@ -2859,7 +2952,7 @@ typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *
*
* The provided pointer can be immediately freed once the function returns.
*
- * @param p_data A pointer to an UTF-8 encoded C string.
+ * @param p_data A pointer to a UTF-8 encoded C string.
* @param p_size The number of bytes (not code units).
*/
typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size);
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index a4d032f22f..1ee9de0776 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -295,6 +295,9 @@ GDExtensionManager::GDExtensionManager() {
}
GDExtensionManager::~GDExtensionManager() {
+ if (singleton == this) {
+ singleton = nullptr;
+ }
#ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes::finalize();
#endif
diff --git a/core/extension/make_wrappers.py b/core/extension/make_wrappers.py
index 655b90d2b1..54f4fd5579 100644
--- a/core/extension/make_wrappers.py
+++ b/core/extension/make_wrappers.py
@@ -10,7 +10,6 @@ _FORCE_INLINE_ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
def generate_mod_version(argcount, const=False, returns=False):
s = proto_mod
sproto = str(argcount)
- method_info = ""
if returns:
sproto += "R"
s = s.replace("$RETTYPE", "m_ret, ")
@@ -68,7 +67,6 @@ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
def generate_ex_version(argcount, const=False, returns=False):
s = proto_ex
sproto = str(argcount)
- method_info = ""
if returns:
sproto += "R"
s = s.replace("$RETTYPE", "m_ret, ")
diff --git a/core/input/SCsub b/core/input/SCsub
index da29637135..d8e6f33156 100644
--- a/core/input/SCsub
+++ b/core/input/SCsub
@@ -4,7 +4,6 @@ Import("env")
import input_builders
-
# Order matters here. Higher index controller database files write on top of lower index database files.
controller_databases = [
"gamecontrollerdb.txt",
diff --git a/core/input/gamecontrollerdb.txt b/core/input/gamecontrollerdb.txt
index a17e15a62c..7150911e75 100644
--- a/core/input/gamecontrollerdb.txt
+++ b/core/input/gamecontrollerdb.txt
@@ -917,6 +917,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00003801000008010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Mac OS X,
030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f0000aa00000072050000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
+030000000d0f00000002000017010000,Hori Split Pad Fit,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00000002000015010000,Hori Switch Split Pad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00006e00000000010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00006600000000010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -1282,6 +1283,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00006b00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00001600000000010000,Hori Real Arcade Pro EXSE,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f0000aa00000011010000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+030000000d0f00008501000017010000,Hori Split Pad Fit,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00008501000015010000,Hori Switch Split Pad Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00006e00000011010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00006600000011010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
@@ -1696,6 +1698,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000130b000017050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
060000005e040000120b00000d050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000014050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt
index f5158bfabb..8e8ec4c718 100644
--- a/core/input/godotcontrollerdb.txt
+++ b/core/input/godotcontrollerdb.txt
@@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back
Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android,
# Web
-standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
+standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
diff --git a/core/input/input.compat.inc b/core/input/input.compat.inc
new file mode 100644
index 0000000000..cbc8b1df0f
--- /dev/null
+++ b/core/input/input.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* input.compat.inc */
+/**************************************************************************/
+/* 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 DISABLE_DEPRECATED
+
+void Input::_vibrate_handheld_bind_compat_91143(int p_duration_ms) {
+ vibrate_handheld(p_duration_ms, -1.0);
+}
+
+void Input::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::_vibrate_handheld_bind_compat_91143, DEFVAL(500));
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/core/input/input.cpp b/core/input/input.cpp
index c24a59203f..eba7ded267 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "input.h"
+#include "input.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/default_controller_mappings.h"
@@ -120,7 +121,7 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration);
ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0));
ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration);
- ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::vibrate_handheld, DEFVAL(500));
+ ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0));
ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity);
ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer);
ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer);
@@ -512,21 +513,49 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
Vector3 Input::get_gravity() const {
_THREAD_SAFE_METHOD_
+
+#ifdef DEBUG_ENABLED
+ if (!gravity_enabled) {
+ WARN_PRINT_ONCE("`input_devices/sensors/enable_gravity` is not enabled in project settings.");
+ }
+#endif
+
return gravity;
}
Vector3 Input::get_accelerometer() const {
_THREAD_SAFE_METHOD_
+
+#ifdef DEBUG_ENABLED
+ if (!accelerometer_enabled) {
+ WARN_PRINT_ONCE("`input_devices/sensors/enable_accelerometer` is not enabled in project settings.");
+ }
+#endif
+
return accelerometer;
}
Vector3 Input::get_magnetometer() const {
_THREAD_SAFE_METHOD_
+
+#ifdef DEBUG_ENABLED
+ if (!magnetometer_enabled) {
+ WARN_PRINT_ONCE("`input_devices/sensors/enable_magnetometer` is not enabled in project settings.");
+ }
+#endif
+
return magnetometer;
}
Vector3 Input::get_gyroscope() const {
_THREAD_SAFE_METHOD_
+
+#ifdef DEBUG_ENABLED
+ if (!gyroscope_enabled) {
+ WARN_PRINT_ONCE("`input_devices/sensors/enable_gyroscope` is not enabled in project settings.");
+ }
+#endif
+
return gyroscope;
}
@@ -757,12 +786,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
bool was_pressed = action_state.cache.pressed;
_update_action_cache(E.key, action_state);
+ // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
if (action_state.cache.pressed && !was_pressed) {
- action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
if (!action_state.cache.pressed && was_pressed) {
- action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
}
}
@@ -803,8 +833,8 @@ void Input::stop_joy_vibration(int p_device) {
joy_vibration[p_device] = vibration;
}
-void Input::vibrate_handheld(int p_duration_ms) {
- OS::get_singleton()->vibrate_handheld(p_duration_ms);
+void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) {
+ OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude);
}
void Input::set_gravity(const Vector3 &p_gravity) {
@@ -857,7 +887,7 @@ void Input::warp_mouse(const Vector2 &p_position) {
warp_mouse_func(p_position);
}
-Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect) {
+Point2 Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect) {
// The relative distance reported for the next event after a warp is in the boundaries of the
// size of the rect on that axis, but it may be greater, in which case there's no problem as fmod()
// will warp it, but if the pointer has moved in the opposite direction between the pointer relocation
@@ -867,14 +897,14 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con
// detect the warp: if the relative distance is greater than the half of the size of the relevant rect
// (checked per each axis), it will be considered as the consequence of a former pointer warp.
- const Point2i rel_sign(p_motion->get_relative().x >= 0.0f ? 1 : -1, p_motion->get_relative().y >= 0.0 ? 1 : -1);
- const Size2i warp_margin = p_rect.size * 0.5f;
- const Point2i rel_warped(
+ const Point2 rel_sign(p_motion->get_relative().x >= 0.0f ? 1 : -1, p_motion->get_relative().y >= 0.0 ? 1 : -1);
+ const Size2 warp_margin = p_rect.size * 0.5f;
+ const Point2 rel_warped(
Math::fmod(p_motion->get_relative().x + rel_sign.x * warp_margin.x, p_rect.size.x) - rel_sign.x * warp_margin.x,
Math::fmod(p_motion->get_relative().y + rel_sign.y * warp_margin.y, p_rect.size.y) - rel_sign.y * warp_margin.y);
- const Point2i pos_local = p_motion->get_global_position() - p_rect.position;
- const Point2i pos_warped(Math::fposmod(pos_local.x, p_rect.size.x), Math::fposmod(pos_local.y, p_rect.size.y));
+ const Point2 pos_local = p_motion->get_global_position() - p_rect.position;
+ const Point2 pos_warped(Math::fposmod(pos_local.x, p_rect.size.x), Math::fposmod(pos_local.y, p_rect.size.y));
if (pos_warped != pos_local) {
warp_mouse(pos_warped + p_rect.position);
}
@@ -888,8 +918,9 @@ void Input::action_press(const StringName &p_action, float p_strength) {
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
+ // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
if (!action_state.cache.pressed) {
- action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
action_state.exact = true;
@@ -906,7 +937,8 @@ void Input::action_release(const StringName &p_action) {
action_state.cache.pressed = 0;
action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0;
- action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
+ action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
action_state.device_states.clear();
action_state.exact = true;
@@ -1021,13 +1053,21 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) {
if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) {
buffered_events.push_back(p_event);
}
- } else if (use_input_buffering) {
+ } else if (agile_input_event_flushing) {
buffered_events.push_back(p_event);
} else {
_parse_input_event_impl(p_event, false);
}
}
+#ifdef DEBUG_ENABLED
+void Input::flush_frame_parsed_events() {
+ _THREAD_SAFE_METHOD_
+
+ frame_parsed_events.clear();
+}
+#endif
+
void Input::flush_buffered_events() {
_THREAD_SAFE_METHOD_
@@ -1044,12 +1084,12 @@ void Input::flush_buffered_events() {
}
}
-bool Input::is_using_input_buffering() {
- return use_input_buffering;
+bool Input::is_agile_input_event_flushing() {
+ return agile_input_event_flushing;
}
-void Input::set_use_input_buffering(bool p_enable) {
- use_input_buffering = p_enable;
+void Input::set_agile_input_event_flushing(bool p_enable) {
+ agile_input_event_flushing = p_enable;
}
void Input::set_use_accumulated_input(bool p_enable) {
@@ -1243,7 +1283,7 @@ void Input::_update_action_cache(const StringName &p_action_name, ActionState &r
r_action_state.cache.strength = 0.0;
r_action_state.cache.raw_strength = 0.0;
- int max_event = InputMap::get_singleton()->action_get_events(p_action_name)->size();
+ int max_event = InputMap::get_singleton()->action_get_events(p_action_name)->size() + 1; // +1 comes from InputEventAction.
for (const KeyValue<int, ActionState::DeviceState> &kv : r_action_state.device_states) {
const ActionState::DeviceState &device_state = kv.value;
for (int i = 0; i < max_event; i++) {
@@ -1495,6 +1535,7 @@ void Input::parse_mapping(const String &p_mapping) {
JoyAxis output_axis = _get_output_axis(output);
if (output_button == JoyButton::INVALID && output_axis == JoyAxis::INVALID) {
print_verbose(vformat("Unrecognized output string \"%s\" in mapping:\n%s", output, p_mapping));
+ continue;
}
ERR_CONTINUE_MSG(output_button != JoyButton::INVALID && output_axis != JoyAxis::INVALID,
vformat("Output string \"%s\" matched both button and axis in mapping:\n%s", output, p_mapping));
@@ -1670,6 +1711,11 @@ Input::Input() {
// Always use standard behavior in the editor.
legacy_just_pressed_behavior = false;
}
+
+ accelerometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_accelerometer", false);
+ gravity_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gravity", false);
+ gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false);
+ magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false);
}
Input::~Input() {
diff --git a/core/input/input.h b/core/input/input.h
index d1f284e8f7..95dd623cc0 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -92,9 +92,13 @@ private:
RBSet<JoyButton> joy_buttons_pressed;
RBMap<JoyAxis, float> _joy_axis;
//RBMap<StringName,int> custom_action_press;
+ bool gravity_enabled = false;
Vector3 gravity;
+ bool accelerometer_enabled = false;
Vector3 accelerometer;
+ bool magnetometer_enabled = false;
Vector3 magnetometer;
+ bool gyroscope_enabled = false;
Vector3 gyroscope;
Vector2 mouse_pos;
int64_t mouse_window = 0;
@@ -128,7 +132,7 @@ private:
bool emulate_touch_from_mouse = false;
bool emulate_mouse_from_touch = false;
- bool use_input_buffering = false;
+ bool agile_input_event_flushing = false;
bool use_accumulated_input = true;
int mouse_from_touch_index = -1;
@@ -264,6 +268,11 @@ private:
EventDispatchFunc event_dispatch_function = nullptr;
+#ifndef DISABLE_DEPRECATED
+ void _vibrate_handheld_bind_compat_91143(int p_duration_ms = 500);
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
protected:
static void _bind_methods();
@@ -311,7 +320,7 @@ public:
BitField<MouseButtonMask> get_mouse_button_mask() const;
void warp_mouse(const Vector2 &p_position);
- Point2i warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect);
+ Point2 warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect);
void parse_input_event(const Ref<InputEvent> &p_event);
@@ -323,7 +332,7 @@ public:
void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0);
void stop_joy_vibration(int p_device);
- void vibrate_handheld(int p_duration_ms = 500);
+ void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0);
void set_mouse_position(const Point2 &p_posf);
@@ -358,9 +367,12 @@ public:
Dictionary get_joy_info(int p_device) const;
void set_fallback_mapping(const String &p_guid);
+#ifdef DEBUG_ENABLED
+ void flush_frame_parsed_events();
+#endif
void flush_buffered_events();
- bool is_using_input_buffering();
- void set_use_input_buffering(bool p_enable);
+ bool is_agile_input_event_flushing();
+ void set_agile_input_event_flushing(bool p_enable);
void set_use_accumulated_input(bool p_enable);
bool is_using_accumulated_input();
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index bf1de8d3b2..de3efa7a3a 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -1585,6 +1585,14 @@ float InputEventAction::get_strength() const {
return strength;
}
+void InputEventAction::set_event_index(int p_index) {
+ event_index = p_index;
+}
+
+int InputEventAction::get_event_index() const {
+ return event_index;
+}
+
bool InputEventAction::is_match(const Ref<InputEvent> &p_event, bool p_exact_match) const {
if (p_event.is_null()) {
return false;
@@ -1649,9 +1657,13 @@ void InputEventAction::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_strength", "strength"), &InputEventAction::set_strength);
ClassDB::bind_method(D_METHOD("get_strength"), &InputEventAction::get_strength);
+ ClassDB::bind_method(D_METHOD("set_event_index", "index"), &InputEventAction::set_event_index);
+ ClassDB::bind_method(D_METHOD("get_event_index"), &InputEventAction::get_event_index);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "action"), "set_action", "get_action");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_strength", "get_strength");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "event_index", PROPERTY_HINT_RANGE, "-1,31,1"), "set_event_index", "get_event_index"); // The max value equals to Input::MAX_EVENT - 1.
}
///////////////////////////////////
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 21b61f3bc2..19176f748e 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -453,6 +453,7 @@ class InputEventAction : public InputEvent {
StringName action;
float strength = 1.0f;
+ int event_index = -1;
protected:
static void _bind_methods();
@@ -466,6 +467,9 @@ public:
void set_strength(float p_strength);
float get_strength() const;
+ void set_event_index(int p_index);
+ int get_event_index() const;
+
virtual bool is_action(const StringName &p_action) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 7fd1806b31..ddeee9d765 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -274,6 +274,13 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str
if (r_raw_strength != nullptr) {
*r_raw_strength = strength;
}
+ if (r_event_index) {
+ if (input_event_action->get_event_index() >= 0) {
+ *r_event_index = input_event_action->get_event_index();
+ } else {
+ *r_event_index = E->value.inputs.size();
+ }
+ }
return input_event_action->get_action() == p_action;
}
@@ -629,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL));
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::HOME));
default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs);
inputs = List<Ref<InputEvent>>();
@@ -638,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL));
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::END));
default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs);
// Text Caret Movement Page Up/Down
@@ -658,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs);
inputs = List<Ref<InputEvent>>();
@@ -666,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs);
// Text Caret Addition Below/Above
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 680a653dfc..2f3fe4deeb 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -84,7 +84,7 @@ static Error _erase_recursive(DirAccess *da) {
String n = da->get_next();
while (!n.is_empty()) {
if (n != "." && n != "..") {
- if (da->current_is_dir()) {
+ if (da->current_is_dir() && !da->is_link(n)) {
dirs.push_back(n);
} else {
files.push_back(n);
@@ -339,6 +339,8 @@ String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
}
Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flags) {
+ ERR_FAIL_COND_V_MSG(p_from == p_to, ERR_INVALID_PARAMETER, "Source and destination path are equal.");
+
//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
Error err;
{
@@ -582,6 +584,10 @@ void DirAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove", "path"), &DirAccess::remove);
ClassDB::bind_static_method("DirAccess", D_METHOD("remove_absolute", "path"), &DirAccess::remove_absolute);
+ ClassDB::bind_method(D_METHOD("is_link", "path"), &DirAccess::is_link);
+ ClassDB::bind_method(D_METHOD("read_link", "path"), &DirAccess::read_link);
+ ClassDB::bind_method(D_METHOD("create_link", "source", "target"), &DirAccess::create_link);
+
ClassDB::bind_method(D_METHOD("set_include_navigational", "enable"), &DirAccess::set_include_navigational);
ClassDB::bind_method(D_METHOD("get_include_navigational"), &DirAccess::get_include_navigational);
ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden);
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index d2a5103953..1cf388b33a 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -867,6 +867,7 @@ void FileAccess::_bind_methods() {
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes);
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string);
+ ClassDB::bind_method(D_METHOD("resize", "length"), &FileAccess::resize);
ClassDB::bind_method(D_METHOD("flush"), &FileAccess::flush);
ClassDB::bind_method(D_METHOD("get_path"), &FileAccess::get_path);
ClassDB::bind_method(D_METHOD("get_path_absolute"), &FileAccess::get_path_absolute);
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 0b631f68b1..2ab84db4b6 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -166,6 +166,7 @@ public:
virtual Error get_error() const = 0; ///< get last error
+ virtual Error resize(int64_t p_length) = 0;
virtual void flush() = 0;
virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h
index bf57eaa07c..f706c82f8e 100644
--- a/core/io/file_access_compressed.h
+++ b/core/io/file_access_compressed.h
@@ -88,6 +88,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h
index 489d213b8f..42afe49a5e 100644
--- a/core/io/file_access_encrypted.h
+++ b/core/io/file_access_encrypted.h
@@ -78,6 +78,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h
index d9efb354c3..e9fbc26d75 100644
--- a/core/io/file_access_memory.h
+++ b/core/io/file_access_memory.h
@@ -61,6 +61,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 991b94db38..02bf0a6039 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -119,6 +119,10 @@ void PackedData::_free_packed_dirs(PackedDir *p_dir) {
}
PackedData::~PackedData() {
+ if (singleton == this) {
+ singleton = nullptr;
+ }
+
for (int i = 0; i < sources.size(); i++) {
memdelete(sources[i]);
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 0deacfebab..594ac8f089 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -177,6 +177,7 @@ public:
virtual Error get_error() const override;
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override;
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index 3c7f67664d..c0d1afc8e1 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -277,7 +277,7 @@ void FileAccessZip::seek_end(int64_t p_position) {
uint64_t FileAccessZip::get_position() const {
ERR_FAIL_NULL_V(zfile, 0);
- return unztell(zfile);
+ return unztell64(zfile);
}
uint64_t FileAccessZip::get_length() const {
diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h
index e9ea74e01d..88b63e93e2 100644
--- a/core/io/file_access_zip.h
+++ b/core/io/file_access_zip.h
@@ -100,6 +100,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 6096211cff..b35d405662 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -300,10 +300,10 @@ int Image::get_format_block_size(Format p_format) {
return 1;
}
-void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const {
+void Image::_get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const {
int w = width;
int h = height;
- int ofs = 0;
+ int64_t ofs = 0;
int pixel_size = get_format_pixel_size(format);
int pixel_rshift = get_format_pixel_rshift(format);
@@ -315,7 +315,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt
int bw = w % block != 0 ? w + (block - w % block) : w;
int bh = h % block != 0 ? h + (block - h % block) : h;
- int s = bw * bh;
+ int64_t s = bw * bh;
s *= pixel_size;
s >>= pixel_rshift;
@@ -329,37 +329,30 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt
r_height = h;
}
-int Image::get_mipmap_offset(int p_mipmap) const {
+int64_t Image::get_mipmap_offset(int p_mipmap) const {
ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1);
- int ofs, w, h;
+ int64_t ofs;
+ int w, h;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
return ofs;
}
-int Image::get_mipmap_byte_size(int p_mipmap) const {
- ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1);
-
- int ofs, w, h;
- _get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
- int ofs2;
- _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h);
- return ofs2 - ofs;
-}
-
-void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const {
- int ofs, w, h;
+void Image::get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const {
+ int64_t ofs;
+ int w, h;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
- int ofs2;
+ int64_t ofs2;
_get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h);
r_ofs = ofs;
r_size = ofs2 - ofs;
}
-void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const {
- int ofs;
+void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const {
+ int64_t ofs;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
- int ofs2, w2, h2;
+ int64_t ofs2;
+ int w2, h2;
_get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2);
r_ofs = ofs;
r_size = ofs2 - ofs;
@@ -508,6 +501,38 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p
}
}
+template <typename T, uint32_t read_channels, uint32_t write_channels, T def_zero, T def_one>
+static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) {
+ uint32_t dst_count = 0;
+ uint32_t src_count = 0;
+
+ const int resolution = p_width * p_height;
+
+ for (int i = 0; i < resolution; i++) {
+ memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T));
+
+ if constexpr (write_channels > read_channels) {
+ const T def_value[4] = { def_zero, def_zero, def_zero, def_one };
+ memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T));
+ }
+
+ dst_count += write_channels;
+ src_count += read_channels;
+ }
+}
+
+static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) {
+ if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) {
+ return true;
+ } else if (p_format0 <= Image::FORMAT_RGBAH && p_format0 >= Image::FORMAT_RH && p_format1 <= Image::FORMAT_RGBAH && p_format1 >= Image::FORMAT_RH) {
+ return true;
+ } else if (p_format0 <= Image::FORMAT_RGBAF && p_format0 >= Image::FORMAT_RF && p_format1 <= Image::FORMAT_RGBAF && p_format1 >= Image::FORMAT_RF) {
+ return true;
+ }
+
+ return false;
+}
+
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.");
if (data.size() == 0) {
@@ -521,10 +546,10 @@ void Image::convert(Format p_new_format) {
// Includes the main image.
const int mipmap_count = get_mipmap_count() + 1;
- if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) {
+ if (Image::is_format_compressed(format) || Image::is_format_compressed(p_new_format)) {
ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead.");
- } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) {
+ } else if (!_are_formats_compatible(format, p_new_format)) {
//use put/set pixel which is slower but works with non byte formats
Image new_img(width, height, mipmaps, p_new_format);
@@ -538,8 +563,8 @@ void Image::convert(Format p_new_format) {
}
}
- int mip_offset = 0;
- int mip_size = 0;
+ int64_t mip_offset = 0;
+ int64_t mip_size = 0;
new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size);
memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size);
@@ -555,8 +580,8 @@ void Image::convert(Format p_new_format) {
int conversion_type = format | p_new_format << 8;
for (int mip = 0; mip < mipmap_count; mip++) {
- int mip_offset = 0;
- int mip_size = 0;
+ int64_t mip_offset = 0;
+ int64_t mip_size = 0;
int mip_width = 0;
int mip_height = 0;
get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height);
@@ -655,6 +680,78 @@ void Image::convert(Format p_new_format) {
case FORMAT_RGBA8 | (FORMAT_RGB8 << 8):
_convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr);
break;
+ case FORMAT_RH | (FORMAT_RGH << 8):
+ _convert_fast<uint16_t, 1, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RH | (FORMAT_RGBH << 8):
+ _convert_fast<uint16_t, 1, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RH | (FORMAT_RGBAH << 8):
+ _convert_fast<uint16_t, 1, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGH | (FORMAT_RH << 8):
+ _convert_fast<uint16_t, 2, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGH | (FORMAT_RGBH << 8):
+ _convert_fast<uint16_t, 2, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGH | (FORMAT_RGBAH << 8):
+ _convert_fast<uint16_t, 2, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBH | (FORMAT_RH << 8):
+ _convert_fast<uint16_t, 3, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBH | (FORMAT_RGH << 8):
+ _convert_fast<uint16_t, 3, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBH | (FORMAT_RGBAH << 8):
+ _convert_fast<uint16_t, 3, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBAH | (FORMAT_RH << 8):
+ _convert_fast<uint16_t, 4, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBAH | (FORMAT_RGH << 8):
+ _convert_fast<uint16_t, 4, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RGBAH | (FORMAT_RGBH << 8):
+ _convert_fast<uint16_t, 4, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr);
+ break;
+ case FORMAT_RF | (FORMAT_RGF << 8):
+ _convert_fast<uint32_t, 1, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RF | (FORMAT_RGBF << 8):
+ _convert_fast<uint32_t, 1, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RF | (FORMAT_RGBAF << 8):
+ _convert_fast<uint32_t, 1, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGF | (FORMAT_RF << 8):
+ _convert_fast<uint32_t, 2, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGF | (FORMAT_RGBF << 8):
+ _convert_fast<uint32_t, 2, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGF | (FORMAT_RGBAF << 8):
+ _convert_fast<uint32_t, 2, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBF | (FORMAT_RF << 8):
+ _convert_fast<uint32_t, 3, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBF | (FORMAT_RGF << 8):
+ _convert_fast<uint32_t, 3, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBF | (FORMAT_RGBAF << 8):
+ _convert_fast<uint32_t, 3, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBAF | (FORMAT_RF << 8):
+ _convert_fast<uint32_t, 4, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBAF | (FORMAT_RGF << 8):
+ _convert_fast<uint32_t, 4, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
+ case FORMAT_RGBAF | (FORMAT_RGBF << 8):
+ _convert_fast<uint32_t, 4, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr);
+ break;
}
}
@@ -1151,7 +1248,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
if (i == 0) {
// Read from the first mipmap that will be interpolated
// (if both levels are the same, we will not interpolate, but at least we'll sample from the right level)
- int offs;
+ int64_t offs;
_get_mipmap_offset_and_size(mip1, offs, src_width, src_height);
src_ptr = r_ptr + offs;
} else if (!interpolate_mipmaps) {
@@ -1159,7 +1256,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
break;
} else {
// Switch to read from the second mipmap that will be interpolated
- int offs;
+ int64_t offs;
_get_mipmap_offset_and_size(mip2, offs, src_width, src_height);
src_ptr = r_ptr + offs;
// Switch to write to the second destination image
@@ -1599,9 +1696,9 @@ void Image::flip_x() {
}
/// Get mipmap size and offset.
-int 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) {
+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).
- int size = 0;
+ int64_t size = 0;
int w = p_width;
int h = p_height;
@@ -1623,7 +1720,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &
int bw = w % block != 0 ? w + (block - w % block) : w;
int bh = h % block != 0 ? h + (block - h % block) : h;
- int s = bw * bh;
+ int64_t s = bw * bh;
s *= pixsize;
s >>= pixshift;
@@ -1662,7 +1759,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &
}
bool Image::_can_modify(Format p_format) const {
- return p_format <= FORMAT_RGBE9995;
+ return !Image::is_format_compressed(p_format);
}
template <typename Component, int CC, bool renormalize,
@@ -1837,7 +1934,8 @@ Error Image::generate_mipmaps(bool p_renormalize) {
int prev_w = width;
for (int i = 1; i <= mmcount; i++) {
- int ofs, w, h;
+ int64_t ofs;
+ int w, h;
_get_mipmap_offset_and_size(i, ofs, w, h);
switch (format) {
@@ -1993,7 +2091,8 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con
uint8_t *base_ptr = data.ptrw();
for (int i = 1; i <= mmcount; i++) {
- int ofs, w, h;
+ int64_t ofs;
+ int w, h;
_get_mipmap_offset_and_size(i, ofs, w, h);
uint8_t *ptr = &base_ptr[ofs];
@@ -2102,21 +2201,6 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con
_set_color_at_ofs(ptr, pixel_ofs, c);
}
}
-#if 0
- {
- int size = get_mipmap_byte_size(i);
- print_line("size for mimpap " + itos(i) + ": " + itos(size));
- Vector<uint8_t> imgdata;
- imgdata.resize(size);
-
-
- uint8_t* wr = imgdata.ptrw();
- memcpy(wr.ptr(), ptr, size);
- wr = uint8_t*();
- Ref<Image> im = Image::create_from_data(w, h, false, format, imgdata);
- im->save_png("res://mipmap_" + itos(i) + ".png");
- }
-#endif
}
return OK;
@@ -2131,7 +2215,8 @@ void Image::clear_mipmaps() {
return;
}
- int ofs, w, h;
+ int64_t ofs;
+ int w, h;
_get_mipmap_offset_and_size(1, ofs, w, h);
data.resize(ofs);
@@ -2176,7 +2261,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
int mm = 0;
- int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
+ int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
data.resize(size);
{
@@ -2202,7 +2287,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
int mm;
- int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
+ int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
if (unlikely(p_data.size() != size)) {
String description_mipmaps = get_format_name(p_format) + " ";
@@ -2405,7 +2490,7 @@ bool Image::is_invisible() const {
return false;
}
- int len = data.size();
+ int64_t len = data.size();
if (len == 0) {
return true;
@@ -2445,7 +2530,7 @@ bool Image::is_invisible() const {
}
Image::AlphaMode Image::detect_alpha() const {
- int len = data.size();
+ int64_t len = data.size();
if (len == 0) {
return ALPHA_NONE;
@@ -2579,7 +2664,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua
return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality);
}
-int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) {
+int64_t Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) {
int mm;
return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0);
}
@@ -2597,7 +2682,7 @@ Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format,
return ret;
}
-int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) {
+int64_t Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) {
if (p_mipmap <= 0) {
return 0;
}
@@ -2605,7 +2690,7 @@ int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, i
return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1);
}
-int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) {
+int64_t Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) {
if (p_mipmap <= 0) {
r_w = p_width;
r_h = p_height;
@@ -2616,7 +2701,11 @@ int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, For
}
bool Image::is_compressed() const {
- return format > FORMAT_RGBE9995;
+ return is_format_compressed(format);
+}
+
+bool Image::is_format_compressed(Format p_format) {
+ return p_format > FORMAT_RGBE9995;
}
Error Image::decompress() {
@@ -3308,7 +3397,7 @@ uint8_t *Image::ptrw() {
return data.ptrw();
}
-int64_t Image::data_size() const {
+int64_t Image::get_data_size() const {
return data.size();
}
@@ -3423,6 +3512,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_mipmaps"), &Image::has_mipmaps);
ClassDB::bind_method(D_METHOD("get_format"), &Image::get_format);
ClassDB::bind_method(D_METHOD("get_data"), &Image::get_data);
+ ClassDB::bind_method(D_METHOD("get_data_size"), &Image::get_data_size);
ClassDB::bind_method(D_METHOD("convert", "format"), &Image::convert);
@@ -3439,7 +3529,10 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("generate_mipmaps", "renormalize"), &Image::generate_mipmaps, DEFVAL(false));
ClassDB::bind_method(D_METHOD("clear_mipmaps"), &Image::clear_mipmaps);
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_static_method("Image", D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::create_empty);
+#endif
+ ClassDB::bind_static_method("Image", D_METHOD("create_empty", "width", "height", "use_mipmaps", "format"), &Image::create_empty);
ClassDB::bind_static_method("Image", D_METHOD("create_from_data", "width", "height", "use_mipmaps", "format", "data"), &Image::create_from_data);
ClassDB::bind_method(D_METHOD("set_data", "width", "height", "use_mipmaps", "format", "data"), &Image::set_data);
@@ -3634,9 +3727,10 @@ Ref<Image> Image::rgbe_to_srgb() {
return new_image;
}
-Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const {
- int ofs, size, w, h;
- get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h);
+Ref<Image> Image::get_image_from_mipmap(int p_mipmap) const {
+ int64_t ofs, size;
+ int w, h;
+ get_mipmap_offset_size_and_dimensions(p_mipmap, ofs, size, w, h);
Vector<uint8_t> new_data;
new_data.resize(size);
diff --git a/core/io/image.h b/core/io/image.h
index 2cabbb767a..d55cc39dbb 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -195,9 +195,9 @@ private:
data = p_image.data;
}
- _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &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 int _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);
+ 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;
_FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const;
@@ -238,10 +238,12 @@ public:
*/
Format get_format() const;
- int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data
- int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data
- void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data
- void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) 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,
@@ -351,11 +353,11 @@ public:
static int get_format_block_size(Format p_format);
static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h);
- static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false);
+ static int64_t get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false);
static int get_image_required_mipmaps(int p_width, int p_height, Format p_format);
static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap);
- static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap);
- static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h);
+ 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,
@@ -376,13 +378,14 @@ public:
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error decompress();
bool is_compressed() const;
+ static bool is_format_compressed(Format p_format);
void fix_alpha_edges();
void premultiply_alpha();
void srgb_to_linear();
void normal_map_to_xy();
Ref<Image> rgbe_to_srgb();
- Ref<Image> get_image_from_mipmap(int p_mipamp) const;
+ Ref<Image> get_image_from_mipmap(int p_mipmap) const;
void bump_map_to_normal_map(float bump_scale = 1.0);
void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
@@ -428,7 +431,7 @@ public:
const uint8_t *ptr() const;
uint8_t *ptrw();
- int64_t data_size() const;
+ int64_t get_data_size() const;
void adjust_bcs(float p_brightness, float p_contrast, float p_saturation);
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index ec86104926..f20d65bef9 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -148,8 +148,8 @@ PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, Type
resolver->mutex.unlock();
PackedStringArray result;
- for (int i = 0; i < res.size(); ++i) {
- result.push_back(String(res[i]));
+ for (const IPAddress &E : res) {
+ result.push_back(String(E));
}
return result;
}
@@ -206,9 +206,9 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
List<IPAddress> res = resolver->queue[p_id].response;
- for (int i = 0; i < res.size(); ++i) {
- if (res[i].is_valid()) {
- return res[i];
+ for (const IPAddress &E : res) {
+ if (E.is_valid()) {
+ return E;
}
}
return IPAddress();
@@ -226,9 +226,9 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
List<IPAddress> res = resolver->queue[p_id].response;
Array result;
- for (int i = 0; i < res.size(); ++i) {
- if (res[i].is_valid()) {
- result.push_back(String(res[i]));
+ for (const IPAddress &E : res) {
+ if (E.is_valid()) {
+ result.push_back(String(E));
}
}
return result;
diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp
index ce74bb36d6..a93876a2b5 100644
--- a/core/io/ip_address.cpp
+++ b/core/io/ip_address.cpp
@@ -202,7 +202,7 @@ IPAddress::IPAddress(const String &p_string) {
// Wildcard (not a valid IP)
wildcard = true;
- } else if (p_string.find(":") >= 0) {
+ } else if (p_string.contains(":")) {
// IPv6
_parse_ipv6(p_string);
valid = true;
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 5a1fe45f70..61051727c1 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -344,10 +344,10 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
r_token.value = number;
return OK;
- } else if (is_ascii_char(p_str[index])) {
+ } else if (is_ascii_alphabet_char(p_str[index])) {
String id;
- while (is_ascii_char(p_str[index])) {
+ while (is_ascii_alphabet_char(p_str[index])) {
id += p_str[index];
index++;
}
diff --git a/core/io/logger.cpp b/core/io/logger.cpp
index 441df7f5d1..a24277fe72 100644
--- a/core/io/logger.cpp
+++ b/core/io/logger.cpp
@@ -37,6 +37,8 @@
#include "core/os/time.h"
#include "core/string/print_string.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
#define sprintf sprintf_s
#endif
@@ -180,6 +182,12 @@ RotatedFileLogger::RotatedFileLogger(const String &p_base_path, int p_max_files)
base_path(p_base_path.simplify_path()),
max_files(p_max_files > 0 ? p_max_files : 1) {
rotate_file();
+
+#ifdef MODULE_REGEX_ENABLED
+ strip_ansi_regex.instantiate();
+ strip_ansi_regex->detach_from_objectdb(); // Note: This RegEx instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
+ strip_ansi_regex->compile("\u001b\\[((?:\\d|;)*)([a-zA-Z])");
+#endif // MODULE_REGEX_ENABLED
}
void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
@@ -199,7 +207,15 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
vsnprintf(buf, len + 1, p_format, list_copy);
}
va_end(list_copy);
+
+#ifdef MODULE_REGEX_ENABLED
+ // Strip ANSI escape codes (such as those inserted by `print_rich()`)
+ // before writing to file, as text editors cannot display those
+ // correctly.
+ file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true));
+#else
file->store_buffer((uint8_t *)buf, len);
+#endif // MODULE_REGEX_ENABLED
if (len >= static_buf_size) {
Memory::free_static(buf);
diff --git a/core/io/logger.h b/core/io/logger.h
index 3cd18965c5..85ef3031ec 100644
--- a/core/io/logger.h
+++ b/core/io/logger.h
@@ -34,6 +34,10 @@
#include "core/io/file_access.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif // MODULE_REGEX_ENABLED
#include <stdarg.h>
@@ -86,6 +90,10 @@ class RotatedFileLogger : public Logger {
void clear_old_backups();
void rotate_file();
+#ifdef MODULE_REGEX_ENABLED
+ Ref<RegEx> strip_ansi_regex;
+#endif // MODULE_REGEX_ENABLED
+
public:
explicit RotatedFileLogger(const String &p_base_path, int p_max_files = 10);
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index 4487b8e472..67469de5cc 100644
--- a/core/io/marshalls.cpp
+++ b/core/io/marshalls.cpp
@@ -30,7 +30,6 @@
#include "marshalls.h"
-#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
@@ -656,10 +655,19 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
ERR_FAIL_COND_V(!ClassDB::can_instantiate(str), ERR_INVALID_DATA);
Object *obj = ClassDB::instantiate(str);
-
ERR_FAIL_NULL_V(obj, ERR_UNAVAILABLE);
- ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
+ // Avoid premature free `RefCounted`. This must be done before properties are initialized,
+ // since script functions (setters, implicit initializer) may be called. See GH-68666.
+ Variant variant;
+ if (Object::cast_to<RefCounted>(obj)) {
+ Ref<RefCounted> ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj));
+ variant = ref;
+ } else {
+ variant = obj;
+ }
+
+ ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
buf += 4;
len -= 4;
@@ -699,12 +707,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
}
}
- if (Object::cast_to<RefCounted>(obj)) {
- Ref<RefCounted> ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj));
- r_variant = ref;
- } else {
- r_variant = obj;
- }
+ r_variant = variant;
}
}
@@ -790,11 +793,11 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
builtin_type = (Variant::Type)bt;
- ERR_FAIL_COND_V(!p_allow_objects && builtin_type == Variant::OBJECT, ERR_UNAUTHORIZED);
+ if (!p_allow_objects && builtin_type == Variant::OBJECT) {
+ class_name = EncodedObjectAsID::get_class_static();
+ }
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: {
- ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED);
-
String str;
Error err = _decode_string(buf, len, r_len, str);
if (err) {
@@ -802,22 +805,28 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
}
builtin_type = Variant::OBJECT;
- class_name = str;
+ if (p_allow_objects) {
+ class_name = str;
+ } else {
+ class_name = EncodedObjectAsID::get_class_static();
+ }
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: {
- ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED);
-
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
- 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 + "'.");
builtin_type = Variant::OBJECT;
- class_name = script->get_instance_base_type();
+ 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.
@@ -1169,6 +1178,73 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = carray;
} break;
+
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
+ int32_t count = decode_uint32(buf);
+ buf += 4;
+ len -= 4;
+
+ Vector<Vector4> varray;
+
+ if (header & HEADER_DATA_FLAG_64) {
+ ERR_FAIL_MUL_OF(count, sizeof(double) * 4, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(count < 0 || count * sizeof(double) * 4 > (size_t)len, ERR_INVALID_DATA);
+
+ if (r_len) {
+ (*r_len) += 4; // Size of count number.
+ }
+
+ if (count) {
+ varray.resize(count);
+ Vector4 *w = varray.ptrw();
+
+ for (int32_t i = 0; i < count; i++) {
+ w[i].x = decode_double(buf + i * sizeof(double) * 4 + sizeof(double) * 0);
+ w[i].y = decode_double(buf + i * sizeof(double) * 4 + sizeof(double) * 1);
+ w[i].z = decode_double(buf + i * sizeof(double) * 4 + sizeof(double) * 2);
+ w[i].w = decode_double(buf + i * sizeof(double) * 4 + sizeof(double) * 3);
+ }
+
+ int adv = sizeof(double) * 4 * count;
+
+ if (r_len) {
+ (*r_len) += adv;
+ }
+ len -= adv;
+ buf += adv;
+ }
+ } else {
+ ERR_FAIL_MUL_OF(count, sizeof(float) * 4, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(count < 0 || count * sizeof(float) * 4 > (size_t)len, ERR_INVALID_DATA);
+
+ if (r_len) {
+ (*r_len) += 4; // Size of count number.
+ }
+
+ if (count) {
+ varray.resize(count);
+ Vector4 *w = varray.ptrw();
+
+ for (int32_t i = 0; i < count; i++) {
+ w[i].x = decode_float(buf + i * sizeof(float) * 4 + sizeof(float) * 0);
+ w[i].y = decode_float(buf + i * sizeof(float) * 4 + sizeof(float) * 1);
+ w[i].z = decode_float(buf + i * sizeof(float) * 4 + sizeof(float) * 2);
+ w[i].w = decode_float(buf + i * sizeof(float) * 4 + sizeof(float) * 3);
+ }
+
+ int adv = sizeof(float) * 4 * count;
+
+ if (r_len) {
+ (*r_len) += adv;
+ }
+ len -= adv;
+ buf += adv;
+ }
+ }
+ r_variant = varray;
+
+ } break;
default: {
ERR_FAIL_V(ERR_BUG);
}
@@ -1239,13 +1315,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
if (array.is_typed()) {
Ref<Script> script = array.get_typed_script();
if (script.is_valid()) {
- ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE);
- header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT;
+ 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()) {
- ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE);
header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else {
- ERR_FAIL_COND_V(!p_full_objects && array.get_typed_builtin() == Variant::OBJECT, ERR_UNAVAILABLE);
+ // 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;
}
}
@@ -1256,6 +1331,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::VECTOR4:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::PACKED_VECTOR4_ARRAY:
case Variant::TRANSFORM2D:
case Variant::TRANSFORM3D:
case Variant::PROJECTION:
@@ -1621,7 +1697,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
Variant value;
- if (E.name == CoreStringNames::get_singleton()->_script) {
+ if (E.name == CoreStringName(script)) {
Ref<Script> script = obj->get_script();
if (script.is_valid()) {
String path = script->get_path();
@@ -1709,12 +1785,18 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
Variant variant = array.get_typed_script();
Ref<Script> script = variant;
if (script.is_valid()) {
- 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);
+ 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(array.get_typed_class_name(), buf, r_len);
+ _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;
@@ -1940,6 +2022,32 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 4 * 4 * len;
} break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ Vector<Vector4> data = p_variant;
+ int len = data.size();
+
+ if (buf) {
+ encode_uint32(len, buf);
+ buf += 4;
+ }
+
+ r_len += 4;
+
+ if (buf) {
+ for (int i = 0; i < len; i++) {
+ Vector4 v = data.get(i);
+
+ encode_real(v.x, &buf[0]);
+ encode_real(v.y, &buf[sizeof(real_t)]);
+ encode_real(v.z, &buf[sizeof(real_t) * 2]);
+ encode_real(v.w, &buf[sizeof(real_t) * 3]);
+ buf += sizeof(real_t) * 4;
+ }
+ }
+
+ r_len += sizeof(real_t) * 4 * len;
+
+ } break;
default: {
ERR_FAIL_V(ERR_BUG);
}
diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp
index 11b0c69774..ca236ce05c 100644
--- a/core/io/packed_data_container.cpp
+++ b/core/io/packed_data_container.cpp
@@ -30,7 +30,6 @@
#include "packed_data_container.h"
-#include "core/core_string_names.h"
#include "core/io/marshalls.h"
Variant PackedDataContainer::getvar(const Variant &p_key, bool *r_valid) const {
@@ -244,6 +243,7 @@ uint32_t PackedDataContainer::_pack(const Variant &p_data, Vector<uint8_t> &tmpd
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY:
+ case Variant::PACKED_VECTOR4_ARRAY:
case Variant::STRING_NAME:
case Variant::NODE_PATH: {
uint32_t pos = tmpdata.size();
diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp
index 32030146bb..fae3de2a98 100644
--- a/core/io/packet_peer_udp.cpp
+++ b/core/io/packet_peer_udp.cpp
@@ -106,7 +106,7 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
}
uint32_t size = 0;
- uint8_t ipv6[16];
+ uint8_t ipv6[16] = {};
rb.read(ipv6, 16, true);
packet_ip.set_ipv6(ipv6);
rb.read((uint8_t *)&packet_port, 4, true);
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 74f18ceee1..432adb88da 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -30,7 +30,6 @@
#include "resource.h"
-#include "core/core_string_names.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "core/math/math_funcs.h"
@@ -41,11 +40,11 @@
#include <stdio.h>
void Resource::emit_changed() {
- if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
- // Let the connection happen on the main thread, later, since signals are not thread-safe.
- call_deferred("emit_signal", CoreStringNames::get_singleton()->changed);
+ if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
+ // Let the connection happen on the call queue, later, since signals are not thread-safe.
+ call_deferred("emit_signal", CoreStringName(changed));
} else {
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
}
@@ -167,24 +166,24 @@ bool Resource::editor_can_reload_from_file() {
}
void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) {
- if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
- // Let the check and connection happen on the main thread, later, since signals are not thread-safe.
+ if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
+ // Let the check and connection happen on the call queue, later, since signals are not thread-safe.
callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags);
return;
}
- if (!is_connected(CoreStringNames::get_singleton()->changed, p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) {
- connect(CoreStringNames::get_singleton()->changed, p_callable, p_flags);
+ if (!is_connected(CoreStringName(changed), p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) {
+ connect(CoreStringName(changed), p_callable, p_flags);
}
}
void Resource::disconnect_changed(const Callable &p_callable) {
- if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
- // Let the check and disconnection happen on the main thread, later, since signals are not thread-safe.
+ if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
+ // Let the check and disconnection happen on the call queue, later, since signals are not thread-safe.
callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable);
return;
}
- if (is_connected(CoreStringNames::get_singleton()->changed, p_callable)) {
- disconnect(CoreStringNames::get_singleton()->changed, p_callable);
+ if (is_connected(CoreStringName(changed), p_callable)) {
+ disconnect(CoreStringName(changed), p_callable);
}
}
@@ -383,7 +382,8 @@ Ref<Resource> Resource::duplicate(bool p_subresources) const {
case Variant::Type::PACKED_FLOAT64_ARRAY:
case Variant::Type::PACKED_STRING_ARRAY:
case Variant::Type::PACKED_VECTOR2_ARRAY:
- case Variant::Type::PACKED_VECTOR3_ARRAY: {
+ case Variant::Type::PACKED_VECTOR3_ARRAY:
+ case Variant::Type::PACKED_VECTOR4_ARRAY: {
r->set(E.name, p.duplicate(p_subresources));
} break;
@@ -569,11 +569,18 @@ Resource::Resource() :
remapped_list(this) {}
Resource::~Resource() {
- if (!path_cache.is_empty()) {
- ResourceCache::lock.lock();
- ResourceCache::resources.erase(path_cache);
- ResourceCache::lock.unlock();
+ if (unlikely(path_cache.is_empty())) {
+ return;
}
+
+ ResourceCache::lock.lock();
+ // Only unregister from the cache if this is the actual resource listed there.
+ // (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.)
+ HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache);
+ if (likely(E && E->value == this)) {
+ ResourceCache::resources.remove(E);
+ }
+ ResourceCache::lock.unlock();
}
HashMap<String, Resource *> ResourceCache::resources;
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index d0a8200546..f71257fa76 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -85,15 +85,17 @@ enum {
VARIANT_VECTOR4 = 50,
VARIANT_VECTOR4I = 51,
VARIANT_PROJECTION = 52,
+ VARIANT_PACKED_VECTOR4_ARRAY = 53,
OBJECT_EMPTY = 0,
OBJECT_EXTERNAL_RESOURCE = 1,
OBJECT_INTERNAL_RESOURCE = 2,
OBJECT_EXTERNAL_RESOURCE_INDEX = 3,
- // Version 2: added 64 bits support for float and int.
- // Version 3: changed nodepath encoding.
- // Version 4: new string ID for ext/subresources, breaks forward compat.
+ // Version 2: Added 64-bit support for float and int.
+ // Version 3: Changed NodePath encoding.
+ // Version 4: New string ID for ext/subresources, breaks forward compat.
// Version 5: Ability to store script class in the header.
- FORMAT_VERSION = 5,
+ // Version 6: Added PackedVector4Array Variant type.
+ FORMAT_VERSION = 6,
FORMAT_VERSION_CAN_RENAME_DEPS = 1,
FORMAT_VERSION_NO_NODEPATH_PROPERTY = 3,
};
@@ -653,6 +655,19 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
r_v = array;
} break;
+ case VARIANT_PACKED_VECTOR4_ARRAY: {
+ uint32_t len = f->get_32();
+
+ Vector<Vector4> array;
+ array.resize(len);
+ Vector4 *w = array.ptrw();
+ static_assert(sizeof(Vector4) == 4 * sizeof(real_t));
+ const Error err = read_reals(reinterpret_cast<real_t *>(w), f, len * 4);
+ ERR_FAIL_COND_V(err != OK, err);
+
+ r_v = array;
+
+ } break;
default: {
ERR_FAIL_V(ERR_FILE_CORRUPT);
} break;
@@ -734,44 +749,54 @@ Error ResourceLoaderBinary::load() {
String t = get_unicode_string();
Ref<Resource> res;
+ Resource *r = nullptr;
- if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
- //use the existing one
- Ref<Resource> cached = ResourceCache::get_ref(path);
- if (cached->get_class() == t) {
- cached->reset_state();
- res = cached;
- }
+ MissingResource *missing_resource = nullptr;
+
+ if (main) {
+ res = ResourceLoader::get_resource_ref_override(local_path);
+ r = res.ptr();
}
+ if (!r) {
+ if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
+ //use the existing one
+ Ref<Resource> cached = ResourceCache::get_ref(path);
+ if (cached->get_class() == t) {
+ cached->reset_state();
+ res = cached;
+ }
+ }
- MissingResource *missing_resource = nullptr;
+ if (res.is_null()) {
+ //did not replace
+
+ Object *obj = ClassDB::instantiate(t);
+ if (!obj) {
+ if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
+ //create a missing resource
+ missing_resource = memnew(MissingResource);
+ missing_resource->set_original_class(t);
+ missing_resource->set_recording_properties(true);
+ obj = missing_resource;
+ } else {
+ error = ERR_FILE_CORRUPT;
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ }
+ }
- if (res.is_null()) {
- //did not replace
-
- Object *obj = ClassDB::instantiate(t);
- if (!obj) {
- if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
- //create a missing resource
- missing_resource = memnew(MissingResource);
- missing_resource->set_original_class(t);
- missing_resource->set_recording_properties(true);
- obj = missing_resource;
- } else {
+ r = Object::cast_to<Resource>(obj);
+ if (!r) {
+ String obj_class = obj->get_class();
error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ memdelete(obj); //bye
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
}
- }
- Resource *r = Object::cast_to<Resource>(obj);
- if (!r) {
- 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 + ".");
+ res = Ref<Resource>(r);
}
+ }
- res = Ref<Resource>(r);
+ if (r) {
if (!path.is_empty()) {
if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); // If got here because the resource with same path has different type, replace it.
@@ -1912,33 +1937,33 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
for (int i = 0; i < len; i++) {
save_unicode_string(f, r[i]);
}
-
} break;
- case Variant::PACKED_VECTOR3_ARRAY: {
- f->store_32(VARIANT_PACKED_VECTOR3_ARRAY);
- Vector<Vector3> arr = p_property;
+
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ f->store_32(VARIANT_PACKED_VECTOR2_ARRAY);
+ Vector<Vector2> arr = p_property;
int len = arr.size();
f->store_32(len);
- const Vector3 *r = arr.ptr();
+ const Vector2 *r = arr.ptr();
for (int i = 0; i < len; i++) {
f->store_real(r[i].x);
f->store_real(r[i].y);
- f->store_real(r[i].z);
}
-
} break;
- case Variant::PACKED_VECTOR2_ARRAY: {
- f->store_32(VARIANT_PACKED_VECTOR2_ARRAY);
- Vector<Vector2> arr = p_property;
+
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ f->store_32(VARIANT_PACKED_VECTOR3_ARRAY);
+ Vector<Vector3> arr = p_property;
int len = arr.size();
f->store_32(len);
- const Vector2 *r = arr.ptr();
+ const Vector3 *r = arr.ptr();
for (int i = 0; i < len; i++) {
f->store_real(r[i].x);
f->store_real(r[i].y);
+ f->store_real(r[i].z);
}
-
} break;
+
case Variant::PACKED_COLOR_ARRAY: {
f->store_32(VARIANT_PACKED_COLOR_ARRAY);
Vector<Color> arr = p_property;
@@ -1953,6 +1978,20 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
}
} break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ f->store_32(VARIANT_PACKED_VECTOR4_ARRAY);
+ Vector<Vector4> arr = p_property;
+ int len = arr.size();
+ f->store_32(len);
+ const Vector4 *r = arr.ptr();
+ for (int i = 0; i < len; i++) {
+ f->store_real(r[i].x);
+ f->store_real(r[i].y);
+ f->store_real(r[i].z);
+ f->store_real(r[i].w);
+ }
+
+ } break;
default: {
ERR_FAIL_MSG("Invalid variant.");
}
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index fcf4a727ca..9e6f3ba314 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -118,9 +118,21 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
}
#endif
- if (r_path_and_type.path.is_empty() || r_path_and_type.type.is_empty()) {
+ if (r_path_and_type.type.is_empty()) {
return ERR_FILE_CORRUPT;
}
+ if (r_path_and_type.path.is_empty()) {
+ // Some importers may not write files to the .godot folder, so the path can be empty.
+ if (r_path_and_type.importer.is_empty()) {
+ return ERR_FILE_CORRUPT;
+ }
+
+ // It's only invalid if the extension for the importer is not empty.
+ Ref<ResourceImporter> importer = get_importer_by_name(r_path_and_type.importer);
+ if (importer.is_null() || !importer->get_save_extension().is_empty()) {
+ return ERR_FILE_CORRUPT;
+ }
+ }
return OK;
}
@@ -410,6 +422,7 @@ void ResourceFormatImporter::get_importers_for_extension(const String &p_extensi
for (const String &F : local_exts) {
if (p_extension.to_lower() == F) {
r_importers->push_back(importers[i]);
+ break;
}
}
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index eea6357084..4988e73624 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -38,8 +38,9 @@
#include "core/os/os.h"
#include "core/os/safe_binary_mutex.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/variant/variant_parser.h"
+#include "servers/rendering_server.h"
#ifdef DEBUG_LOAD_THREADED
#define print_lt(m_text) print_line(m_text)
@@ -242,16 +243,20 @@ ResourceLoader::LoadToken::~LoadToken() {
}
Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) {
+ const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
load_nesting++;
if (load_paths_stack->size()) {
thread_load_mutex.lock();
- HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack->get(load_paths_stack->size() - 1));
- if (E) {
+ const String &parent_task_path = load_paths_stack->get(load_paths_stack->size() - 1);
+ HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
+ // Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
+ bool is_remapped_load = original_path == parent_task_path;
+ if (E && !is_remapped_load) {
E->value.sub_tasks.insert(p_original_path);
}
thread_load_mutex.unlock();
}
- load_paths_stack->push_back(p_original_path);
+ load_paths_stack->push_back(original_path);
// Try all loaders and pick the first match for the type hint
bool found = false;
@@ -261,13 +266,14 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
continue;
}
found = true;
- res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
+ res = loader[i]->load(p_path, original_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
if (!res.is_null()) {
break;
}
}
load_paths_stack->resize(load_paths_stack->size() - 1);
+ res_ref_overrides.erase(load_nesting);
load_nesting--;
if (!res.is_null()) {
@@ -298,37 +304,33 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
thread_load_mutex.unlock();
// Thread-safe either if it's the current thread or a brand new one.
- CallQueue *mq_override = nullptr;
+ CallQueue *own_mq_override = nullptr;
if (load_nesting == 0) {
load_paths_stack = memnew(Vector<String>);
- if (!load_task.dependent_path.is_empty()) {
- load_paths_stack->push_back(load_task.dependent_path);
- }
if (!Thread::is_main_thread()) {
- mq_override = memnew(CallQueue);
- MessageQueue::set_thread_singleton_override(mq_override);
+ // Let the caller thread use its own, for added flexibility. Provide one otherwise.
+ if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) {
+ own_mq_override = memnew(CallQueue);
+ MessageQueue::set_thread_singleton_override(own_mq_override);
+ }
set_current_thread_safe_for_nodes(true);
}
- } else {
- DEV_ASSERT(load_task.dependent_path.is_empty());
}
// --
- if (!Thread::is_main_thread()) {
- set_current_thread_safe_for_nodes(true);
- }
-
- Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress);
- if (mq_override) {
- mq_override->flush();
+ Error load_err = OK;
+ Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress);
+ if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) {
+ MessageQueue::get_singleton()->flush();
}
thread_load_mutex.lock();
load_task.resource = res;
- load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0
+ load_task.progress = 1.0; // It was fully loaded at this point, so force progress to 1.0.
+ load_task.error = load_err;
if (load_task.error != OK) {
load_task.status = THREAD_LOAD_FAILED;
} else {
@@ -343,7 +345,14 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP;
bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP;
+ bool unlock_pending = true;
if (load_task.resource.is_valid()) {
+ // From now on, no critical section needed as no one will write to the task anymore.
+ // Moreover, the mutex being unlocked is a requirement if some of the calls below
+ // that set the resource up invoke code that in turn requests resource loading.
+ thread_load_mutex.unlock();
+ unlock_pending = false;
+
if (!ignoring) {
if (replacing) {
Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path);
@@ -381,19 +390,28 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
load_task.status = THREAD_LOAD_LOADED;
load_task.progress = 1.0;
+ thread_load_mutex.unlock();
+ unlock_pending = false;
+
if (_loaded_callback) {
_loaded_callback(load_task.resource, load_task.local_path);
}
}
}
- thread_load_mutex.unlock();
+ if (unlock_pending) {
+ thread_load_mutex.unlock();
+ }
if (load_nesting == 0) {
- if (mq_override) {
- memdelete(mq_override);
+ if (own_mq_override) {
+ MessageQueue::set_thread_singleton_override(nullptr);
+ memdelete(own_mq_override);
+ }
+ if (load_paths_stack) {
+ memdelete(load_paths_stack);
+ load_paths_stack = nullptr;
}
- memdelete(load_paths_stack);
}
}
@@ -453,6 +471,8 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) {
String local_path = _validate_local_path(p_path);
+ bool ignoring_cache = p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP;
+
Ref<LoadToken> load_token;
bool must_not_register = false;
ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load.
@@ -461,16 +481,14 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
{
MutexLock thread_load_lock(thread_load_mutex);
- if (thread_load_tasks.has(local_path)) {
+ if (!ignoring_cache && thread_load_tasks.has(local_path)) {
load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token);
- if (!load_token.is_valid()) {
+ if (load_token.is_valid()) {
+ return load_token;
+ } else {
// The token is dying (reached 0 on another thread).
// Ensure it's killed now so the path can be safely reused right away.
thread_load_tasks[local_path].load_token->clear();
- } else {
- if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- return load_token;
- }
}
}
@@ -499,16 +517,17 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
}
}
- // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously.
- must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE;
+ // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish within scope.
+ must_not_register = ignoring_cache && thread_load_tasks.has(local_path);
if (must_not_register) {
load_token->local_path.clear();
unregistered_load_task = load_task;
+ load_task_ptr = &unregistered_load_task;
} else {
- thread_load_tasks[local_path] = load_task;
+ DEV_ASSERT(!thread_load_tasks.has(local_path));
+ HashMap<String, ResourceLoader::ThreadLoadTask>::Iterator E = thread_load_tasks.insert(local_path, load_task);
+ load_task_ptr = &E->value;
}
-
- load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path];
}
run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT;
@@ -553,27 +572,38 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) {
}
ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) {
- MutexLock thread_load_lock(thread_load_mutex);
+ bool ensure_progress = false;
+ ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
- if (!user_load_tokens.has(p_path)) {
- print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected.");
- return THREAD_LOAD_INVALID_RESOURCE;
- }
+ if (!user_load_tokens.has(p_path)) {
+ print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected.");
+ return THREAD_LOAD_INVALID_RESOURCE;
+ }
- String local_path = _validate_local_path(p_path);
- if (!thread_load_tasks.has(local_path)) {
-#ifdef DEV_ENABLED
- CRASH_NOW();
-#endif
- // On non-dev, be defensive and at least avoid crashing (at this point at least).
- return THREAD_LOAD_INVALID_RESOURCE;
+ String local_path = _validate_local_path(p_path);
+ ERR_FAIL_COND_V_MSG(!thread_load_tasks.has(local_path), THREAD_LOAD_INVALID_RESOURCE, "Bug in ResourceLoader logic, please report.");
+
+ ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ status = load_task.status;
+ if (r_progress) {
+ *r_progress = _dependency_get_progress(local_path);
+ }
+
+ // Support userland polling in a loop on the main thread.
+ if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) {
+ uint64_t frame = Engine::get_singleton()->get_process_frames();
+ if (frame == load_task.last_progress_check_main_thread_frame) {
+ ensure_progress = true;
+ } else {
+ load_task.last_progress_check_main_thread_frame = frame;
+ }
+ }
}
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
- ThreadLoadStatus status;
- status = load_task.status;
- if (r_progress) {
- *r_progress = _dependency_get_progress(local_path);
+ if (ensure_progress) {
+ _ensure_load_progress();
}
return status;
@@ -604,6 +634,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e
}
return Ref<Resource>();
}
+
+ // Support userland requesting on the main thread before the load is reported to be complete.
+ if (Thread::is_main_thread() && !load_token->local_path.is_empty()) {
+ const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path];
+ while (load_task.status == THREAD_LOAD_IN_PROGRESS) {
+ thread_load_lock.~MutexLock();
+ bool exit = !_ensure_load_progress();
+ OS::get_singleton()->delay_usec(1000);
+ new (&thread_load_lock) MutexLock(thread_load_mutex);
+ if (exit) {
+ break;
+ }
+ }
+ }
+
res = _load_complete_inner(*load_token, r_error, thread_load_lock);
if (load_token->unreference()) {
memdelete(load_token);
@@ -627,14 +672,10 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
if (!p_load_token.local_path.is_empty()) {
if (!thread_load_tasks.has(p_load_token.local_path)) {
-#ifdef DEV_ENABLED
- CRASH_NOW();
-#endif
- // On non-dev, be defensive and at least avoid crashing (at this point at least).
if (r_error) {
*r_error = ERR_BUG;
}
- return Ref<Resource>();
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Bug in ResourceLoader logic, please report.");
}
ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path];
@@ -652,39 +693,50 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
return Ref<Resource>();
}
- if (load_task.task_id != 0) {
+ bool loader_is_wtp = load_task.task_id != 0;
+ Error wtp_task_err = FAILED;
+ if (loader_is_wtp) {
// Loading thread is in the worker pool.
+ load_task.awaited = true;
thread_load_mutex.unlock();
- Error err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id);
- if (err == ERR_BUSY) {
- // The WorkerThreadPool has reported that the current task wants to await on an older one.
- // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of
- // resource loading that means that the task to wait for can be restarted here to break the
- // cycle, with as much recursion into this process as needed.
- // When the stack is eventually unrolled, the original load will have been notified to go on.
- // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's
- // an ongoing load for that resource and wait for it again. This value forces a new load.
- Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE);
- Ref<Resource> resource = _load_complete(*token.ptr(), &err);
- if (r_error) {
- *r_error = err;
+ wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id);
+ }
+
+ if (load_task.status == THREAD_LOAD_IN_PROGRESS) { // If early errored, awaiting would deadlock.
+ if (loader_is_wtp) {
+ if (wtp_task_err == ERR_BUSY) {
+ // The WorkerThreadPool has reported that the current task wants to await on an older one.
+ // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of
+ // resource loading that means that the task to wait for can be restarted here to break the
+ // cycle, with as much recursion into this process as needed.
+ // When the stack is eventually unrolled, the original load will have been notified to go on.
+ // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's
+ // an ongoing load for that resource and wait for it again. This value forces a new load.
+ Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE);
+ Ref<Resource> resource = _load_complete(*token.ptr(), &wtp_task_err);
+ if (r_error) {
+ *r_error = wtp_task_err;
+ }
+ thread_load_mutex.lock();
+ return resource;
+ } else {
+ DEV_ASSERT(wtp_task_err == OK);
+ thread_load_mutex.lock();
}
- thread_load_mutex.lock();
- return resource;
} else {
- DEV_ASSERT(err == OK);
- thread_load_mutex.lock();
- load_task.awaited = true;
+ // Loading thread is main or user thread.
+ if (!load_task.cond_var) {
+ load_task.cond_var = memnew(ConditionVariable);
+ }
+ do {
+ load_task.cond_var->wait(p_thread_load_lock);
+ DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count());
+ } while (load_task.cond_var);
}
} else {
- // Loading thread is main or user thread.
- if (!load_task.cond_var) {
- load_task.cond_var = memnew(ConditionVariable);
+ if (loader_is_wtp) {
+ thread_load_mutex.lock();
}
- do {
- load_task.cond_var->wait(p_thread_load_lock);
- DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count());
- } while (load_task.cond_var);
}
}
@@ -711,6 +763,51 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
}
}
+bool ResourceLoader::_ensure_load_progress() {
+ // Some servers may need a new engine iteration to allow the load to progress.
+ // Since the only known one is the rendering server (in single thread mode), let's keep it simple and just sync it.
+ // This may be refactored in the future to support other servers and have less coupling.
+ if (OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD) {
+ return false; // Not needed.
+ }
+ RenderingServer::get_singleton()->sync();
+ return true;
+}
+
+Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) {
+ ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0.
+ const String &local_path = _validate_local_path(p_path);
+ HashMap<String, Ref<Resource>> &overrides = res_ref_overrides[load_nesting - 1];
+ HashMap<String, Ref<Resource>>::Iterator E = overrides.find(local_path);
+ if (E) {
+ return E->value;
+ } else {
+ Object *obj = ClassDB::instantiate(p_res_type);
+ ERR_FAIL_NULL_V(obj, Ref<Resource>());
+ Ref<Resource> res(obj);
+ if (!res.is_valid()) {
+ memdelete(obj);
+ ERR_FAIL_V(Ref<Resource>());
+ }
+ overrides[local_path] = res;
+ return res;
+ }
+}
+
+Ref<Resource> ResourceLoader::get_resource_ref_override(const String &p_path) {
+ DEV_ASSERT(p_path == _validate_local_path(p_path));
+ HashMap<int, HashMap<String, Ref<Resource>>>::Iterator E = res_ref_overrides.find(load_nesting);
+ if (!E) {
+ return nullptr;
+ }
+ HashMap<String, Ref<Resource>>::Iterator F = E->value.find(p_path);
+ if (!F) {
+ return nullptr;
+ }
+
+ return F->value;
+}
+
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
String local_path = _validate_local_path(p_path);
@@ -1202,7 +1299,8 @@ bool ResourceLoader::timestamp_on_load = false;
thread_local int ResourceLoader::load_nesting = 0;
thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0;
-thread_local Vector<String> *ResourceLoader::load_paths_stack;
+thread_local Vector<String> *ResourceLoader::load_paths_stack = nullptr;
+thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides;
template <>
thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 5caf699d32..5f1831f0d9 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -170,10 +170,10 @@ private:
LoadToken *load_token = nullptr;
String local_path;
String remapped_path;
- String dependent_path;
String type_hint;
float progress = 0.0f;
float max_reported_progress = 0.0f;
+ uint64_t last_progress_check_main_thread_frame = UINT64_MAX;
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
Error error = OK;
@@ -187,6 +187,7 @@ private:
static thread_local int load_nesting;
static thread_local WorkerThreadPool::TaskID caller_task_id;
+ static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level.
static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor.
static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
static HashMap<String, ThreadLoadTask> thread_load_tasks;
@@ -196,6 +197,8 @@ private:
static float _dependency_get_progress(const String &p_path);
+ static bool _ensure_load_progress();
+
public:
static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE);
static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr);
@@ -226,7 +229,7 @@ public:
// Loaders can safely use this regardless which thread they are running on.
static void notify_load_error(const String &p_err) {
if (err_notify) {
- callable_mp_static(err_notify).bind(p_err).call_deferred();
+ MessageQueue::get_main_singleton()->push_callable(callable_mp_static(err_notify).bind(p_err));
}
}
static void set_error_notify_func(ResourceLoadErrorNotify p_err_notify) {
@@ -239,7 +242,7 @@ public:
if (Thread::get_caller_id() == Thread::get_main_id()) {
dep_err_notify(p_path, p_dependency, p_type);
} else {
- callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type).call_deferred();
+ MessageQueue::get_main_singleton()->push_callable(callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type));
}
}
}
@@ -272,6 +275,9 @@ public:
static void set_create_missing_resources_if_class_unavailable(bool p_enable);
_FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; }
+ static Ref<Resource> ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type);
+ static Ref<Resource> get_resource_ref_override(const String &p_path);
+
static bool is_cleaning_tasks();
static void initialize();
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index e4022b2073..1dc1245355 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -98,6 +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 + "'.");
String path = p_path;
if (path.is_empty()) {
path = p_resource->get_path();
@@ -174,6 +175,7 @@ void ResourceSaver::set_save_callback(ResourceSavedCallback p_callback) {
}
void ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) {
+ ERR_FAIL_COND_MSG(p_resource.is_null(), "It's not a reference to a valid Resource object.");
for (int i = 0; i < saver_count; i++) {
saver[i]->get_recognized_extensions(p_resource, p_extensions);
}
diff --git a/core/io/stream_peer_tcp.cpp b/core/io/stream_peer_tcp.cpp
index 2b9487b9e1..90a8f49a75 100644
--- a/core/io/stream_peer_tcp.cpp
+++ b/core/io/stream_peer_tcp.cpp
@@ -51,6 +51,7 @@ Error StreamPeerTCP::poll() {
status = STATUS_ERROR;
return err;
}
+ return OK;
} else if (status != STATUS_CONNECTING) {
return OK;
}
diff --git a/core/io/udp_server.cpp b/core/io/udp_server.cpp
index 215c6903a6..75ba784dbd 100644
--- a/core/io/udp_server.cpp
+++ b/core/io/udp_server.cpp
@@ -161,7 +161,7 @@ Ref<PacketPeerUDP> UDPServer::take_connection() {
return conn;
}
- Peer peer = pending[0];
+ Peer peer = pending.front()->get();
pending.pop_front();
peers.push_back(peer);
return peer.peer;
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index f272407869..984bb1c9c1 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -122,6 +122,10 @@ AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const {
}
void AStarGrid2D::update() {
+ if (!dirty) {
+ return;
+ }
+
points.clear();
const int32_t end_x = region.get_end().x;
diff --git a/core/math/aabb.cpp b/core/math/aabb.cpp
index 76e9e74dea..7d1d7c5648 100644
--- a/core/math/aabb.cpp
+++ b/core/math/aabb.cpp
@@ -117,55 +117,75 @@ AABB AABB::intersection(const AABB &p_aabb) const {
return AABB(min, max - min);
}
-bool AABB::intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, Vector3 *r_clip, Vector3 *r_normal) const {
+// Note that this routine returns the BACKTRACKED (i.e. behind the ray origin)
+// intersection point + normal if INSIDE the AABB.
+// The caller can therefore decide when INSIDE whether to use the
+// backtracked intersection, or use p_from as the intersection, and
+// carry on progressing without e.g. reflecting against the normal.
+bool AABB::find_intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, bool &r_inside, Vector3 *r_intersection_point, Vector3 *r_normal) const {
#ifdef MATH_CHECKS
if (unlikely(size.x < 0 || size.y < 0 || size.z < 0)) {
ERR_PRINT("AABB size is negative, this is not supported. Use AABB.abs() to get an AABB with a positive size.");
}
#endif
- Vector3 c1, c2;
Vector3 end = position + size;
- real_t depth_near = -1e20;
- real_t depth_far = 1e20;
+ real_t tmin = -1e20;
+ real_t tmax = 1e20;
int axis = 0;
+ // Make sure r_inside is always initialized,
+ // to prevent reading uninitialized data in the client code.
+ r_inside = false;
+
for (int i = 0; i < 3; i++) {
if (p_dir[i] == 0) {
if ((p_from[i] < position[i]) || (p_from[i] > end[i])) {
return false;
}
} else { // ray not parallel to planes in this direction
- c1[i] = (position[i] - p_from[i]) / p_dir[i];
- c2[i] = (end[i] - p_from[i]) / p_dir[i];
+ real_t t1 = (position[i] - p_from[i]) / p_dir[i];
+ real_t t2 = (end[i] - p_from[i]) / p_dir[i];
- if (c1[i] > c2[i]) {
- SWAP(c1, c2);
+ if (t1 > t2) {
+ SWAP(t1, t2);
}
- if (c1[i] > depth_near) {
- depth_near = c1[i];
+ if (t1 >= tmin) {
+ tmin = t1;
axis = i;
}
- if (c2[i] < depth_far) {
- depth_far = c2[i];
+ if (t2 < tmax) {
+ if (t2 < 0) {
+ return false;
+ }
+ tmax = t2;
}
- if ((depth_near > depth_far) || (depth_far < 0)) {
+ if (tmin > tmax) {
return false;
}
}
}
- if (r_clip) {
- *r_clip = c1;
+ // Did the ray start from inside the box?
+ // In which case the intersection returned is the point of entry
+ // (behind the ray start) or the calling routine can use the ray origin as intersection point.
+ r_inside = tmin < 0;
+
+ if (r_intersection_point) {
+ *r_intersection_point = p_from + p_dir * tmin;
+
+ // Prevent float error by making sure the point is exactly
+ // on the AABB border on the relevant axis.
+ r_intersection_point->coord[axis] = (p_dir[axis] >= 0) ? position.coord[axis] : end.coord[axis];
}
if (r_normal) {
*r_normal = Vector3();
- (*r_normal)[axis] = p_dir[axis] ? -1 : 1;
+ (*r_normal)[axis] = (p_dir[axis] >= 0) ? -1 : 1;
}
return true;
}
-bool AABB::intersects_segment(const Vector3 &p_from, const Vector3 &p_to, Vector3 *r_clip, Vector3 *r_normal) const {
+bool AABB::intersects_segment(const Vector3 &p_from, const Vector3 &p_to, Vector3 *r_intersection_point, Vector3 *r_normal) const {
#ifdef MATH_CHECKS
if (unlikely(size.x < 0 || size.y < 0 || size.z < 0)) {
ERR_PRINT("AABB size is negative, this is not supported. Use AABB.abs() to get an AABB with a positive size.");
@@ -223,8 +243,8 @@ bool AABB::intersects_segment(const Vector3 &p_from, const Vector3 &p_to, Vector
*r_normal = normal;
}
- if (r_clip) {
- *r_clip = p_from + rel * min;
+ if (r_intersection_point) {
+ *r_intersection_point = p_from + rel * min;
}
return true;
@@ -410,7 +430,15 @@ Variant AABB::intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to
Variant AABB::intersects_ray_bind(const Vector3 &p_from, const Vector3 &p_dir) const {
Vector3 inters;
- if (intersects_ray(p_from, p_dir, &inters)) {
+ bool inside = false;
+
+ if (find_intersects_ray(p_from, p_dir, inside, &inters)) {
+ // When inside the intersection point may be BEHIND the ray,
+ // so for general use we return the ray origin.
+ if (inside) {
+ return p_from;
+ }
+
return inters;
}
return Variant();
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 48a883e64c..cb358ca7ef 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -41,7 +41,7 @@
class Variant;
-struct _NO_DISCARD_ AABB {
+struct [[nodiscard]] AABB {
Vector3 position;
Vector3 size;
@@ -71,10 +71,15 @@ struct _NO_DISCARD_ AABB {
AABB merge(const AABB &p_with) const;
void merge_with(const AABB &p_aabb); ///merge with another AABB
AABB intersection(const AABB &p_aabb) const; ///get box where two intersect, empty if no intersection occurs
- bool intersects_segment(const Vector3 &p_from, const Vector3 &p_to, Vector3 *r_clip = nullptr, Vector3 *r_normal = nullptr) const;
- bool intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, Vector3 *r_clip = nullptr, Vector3 *r_normal = nullptr) const;
_FORCE_INLINE_ bool smits_intersect_ray(const Vector3 &p_from, const Vector3 &p_dir, real_t p_t0, real_t p_t1) const;
+ bool intersects_segment(const Vector3 &p_from, const Vector3 &p_to, Vector3 *r_intersection_point = nullptr, Vector3 *r_normal = nullptr) const;
+ bool intersects_ray(const Vector3 &p_from, const Vector3 &p_dir) const {
+ bool inside;
+ return find_intersects_ray(p_from, p_dir, inside);
+ }
+ bool find_intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, bool &r_inside, Vector3 *r_intersection_point = nullptr, Vector3 *r_normal = nullptr) const;
+
_FORCE_INLINE_ bool intersects_convex_shape(const Plane *p_planes, int p_plane_count, const Vector3 *p_points, int p_point_count) const;
_FORCE_INLINE_ bool inside_convex_shape(const Plane *p_planes, int p_plane_count) const;
bool intersects_plane(const Plane &p_plane) const;
@@ -101,7 +106,7 @@ struct _NO_DISCARD_ AABB {
_FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */
_FORCE_INLINE_ AABB abs() const {
- return AABB(position + size.min(Vector3()), size.abs());
+ return AABB(position + size.minf(0), size.abs());
}
Variant intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to) const;
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 84ac878172..34ed1c2d85 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -293,7 +293,7 @@ Vector3 Basis::get_scale_abs() const {
Vector3(rows[0][2], rows[1][2], rows[2][2]).length());
}
-Vector3 Basis::get_scale_local() const {
+Vector3 Basis::get_scale_global() const {
real_t det_sign = SIGN(determinant());
return det_sign * Vector3(rows[0].length(), rows[1].length(), rows[2].length());
}
diff --git a/core/math/basis.h b/core/math/basis.h
index 79f3bda8f8..5c1a5fbdda 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -34,7 +34,7 @@
#include "core/math/quaternion.h"
#include "core/math/vector3.h"
-struct _NO_DISCARD_ Basis {
+struct [[nodiscard]] Basis {
Vector3 rows[3] = {
Vector3(1, 0, 0),
Vector3(0, 1, 0),
@@ -103,7 +103,7 @@ struct _NO_DISCARD_ Basis {
Vector3 get_scale() const;
Vector3 get_scale_abs() const;
- Vector3 get_scale_local() const;
+ Vector3 get_scale_global() const;
void set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Vector3 &p_scale);
void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order = EulerOrder::YXZ);
diff --git a/core/math/color.cpp b/core/math/color.cpp
index d36306d968..1638acd74d 100644
--- a/core/math/color.cpp
+++ b/core/math/color.cpp
@@ -33,7 +33,7 @@
#include "color_names.inc"
#include "core/math/math_funcs.h"
#include "core/string/ustring.h"
-#include "core/templates/rb_map.h"
+#include "core/templates/hash_map.h"
#include "thirdparty/misc/ok_color.h"
@@ -414,7 +414,7 @@ Color Color::named(const String &p_name, const Color &p_default) {
int Color::find_named_color(const String &p_name) {
String name = p_name;
- // Normalize name
+ // Normalize name.
name = name.replace(" ", "");
name = name.replace("-", "");
name = name.replace("_", "");
@@ -422,23 +422,24 @@ int Color::find_named_color(const String &p_name) {
name = name.replace(".", "");
name = name.to_upper();
- int idx = 0;
- while (named_colors[idx].name != nullptr) {
- if (name == String(named_colors[idx].name).replace("_", "")) {
- return idx;
+ static HashMap<String, int> named_colors_hashmap;
+ if (unlikely(named_colors_hashmap.is_empty())) {
+ const int named_color_count = get_named_color_count();
+ for (int i = 0; i < named_color_count; i++) {
+ named_colors_hashmap[String(named_colors[i].name).replace("_", "")] = i;
}
- idx++;
+ }
+
+ const HashMap<String, int>::ConstIterator E = named_colors_hashmap.find(name);
+ if (E) {
+ return E->value;
}
return -1;
}
int Color::get_named_color_count() {
- int idx = 0;
- while (named_colors[idx].name != nullptr) {
- idx++;
- }
- return idx;
+ return sizeof(named_colors) / sizeof(NamedColor);
}
String Color::get_named_color_name(int p_idx) {
diff --git a/core/math/color.h b/core/math/color.h
index 65d7377c1c..70fad78acb 100644
--- a/core/math/color.h
+++ b/core/math/color.h
@@ -35,7 +35,7 @@
class String;
-struct _NO_DISCARD_ Color {
+struct [[nodiscard]] Color {
union {
struct {
float r;
@@ -129,33 +129,46 @@ struct _NO_DISCARD_ Color {
}
_FORCE_INLINE_ uint32_t to_rgbe9995() const {
- const float pow2to9 = 512.0f;
- const float B = 15.0f;
- const float N = 9.0f;
-
- float sharedexp = 65408.000f; // Result of: ((pow2to9 - 1.0f) / pow2to9) * powf(2.0f, 31.0f - 15.0f)
-
- float cRed = MAX(0.0f, MIN(sharedexp, r));
- float cGreen = MAX(0.0f, MIN(sharedexp, g));
- float cBlue = MAX(0.0f, MIN(sharedexp, b));
-
- float cMax = MAX(cRed, MAX(cGreen, cBlue));
-
- float expp = MAX(-B - 1.0f, floor(Math::log(cMax) / (real_t)Math_LN2)) + 1.0f + B;
-
- float sMax = (float)floor((cMax / Math::pow(2.0f, expp - B - N)) + 0.5f);
-
- float exps = expp + 1.0f;
-
- if (0.0f <= sMax && sMax < pow2to9) {
- exps = expp;
- }
-
- float sRed = Math::floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
- float sGreen = Math::floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
- float sBlue = Math::floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
-
- return (uint32_t(Math::fast_ftoi(sRed)) & 0x1FF) | ((uint32_t(Math::fast_ftoi(sGreen)) & 0x1FF) << 9) | ((uint32_t(Math::fast_ftoi(sBlue)) & 0x1FF) << 18) | ((uint32_t(Math::fast_ftoi(exps)) & 0x1F) << 27);
+ // https://github.com/microsoft/DirectX-Graphics-Samples/blob/v10.0.19041.0/MiniEngine/Core/Color.cpp
+ static const float kMaxVal = float(0x1FF << 7);
+ static const float kMinVal = float(1.f / (1 << 16));
+
+ // Clamp RGB to [0, 1.FF*2^16]
+ const float _r = CLAMP(r, 0.0f, kMaxVal);
+ const float _g = CLAMP(g, 0.0f, kMaxVal);
+ const float _b = CLAMP(b, 0.0f, kMaxVal);
+
+ // Compute the maximum channel, no less than 1.0*2^-15
+ const float MaxChannel = MAX(MAX(_r, _g), MAX(_b, kMinVal));
+
+ // Take the exponent of the maximum channel (rounding up the 9th bit) and
+ // add 15 to it. When added to the channels, it causes the implicit '1.0'
+ // bit and the first 8 mantissa bits to be shifted down to the low 9 bits
+ // of the mantissa, rounding the truncated bits.
+ union {
+ float f;
+ int32_t i;
+ } R, G, B, E;
+
+ E.f = MaxChannel;
+ E.i += 0x07804000; // Add 15 to the exponent and 0x4000 to the mantissa
+ E.i &= 0x7F800000; // Zero the mantissa
+
+ // This shifts the 9-bit values we need into the lowest bits, rounding as
+ // needed. Note that if the channel has a smaller exponent than the max
+ // channel, it will shift even more. This is intentional.
+ R.f = _r + E.f;
+ G.f = _g + E.f;
+ B.f = _b + E.f;
+
+ // Convert the Bias to the correct exponent in the upper 5 bits.
+ E.i <<= 4;
+ E.i += 0x10000000;
+
+ // Combine the fields. RGB floats have unwanted data in the upper 9
+ // bits. Only red needs to mask them off because green and blue shift
+ // it out to the left.
+ return E.i | (B.i << 18) | (G.i << 9) | (R.i & 511);
}
_FORCE_INLINE_ Color blend(const Color &p_over) const {
diff --git a/core/math/color_names.inc b/core/math/color_names.inc
index eaa1b1087f..6c0d2a4bfd 100644
--- a/core/math/color_names.inc
+++ b/core/math/color_names.inc
@@ -189,5 +189,4 @@ static NamedColor named_colors[] = {
{ "WHITE_SMOKE", Color::hex(0xF5F5F5FF) },
{ "YELLOW", Color::hex(0xFFFF00FF) },
{ "YELLOW_GREEN", Color::hex(0x9ACD32FF) },
- { nullptr, Color() },
};
diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h
index 45571fb570..4f21a665de 100644
--- a/core/math/delaunay_3d.h
+++ b/core/math/delaunay_3d.h
@@ -46,7 +46,8 @@ class Delaunay3D {
struct Simplex;
enum {
- ACCEL_GRID_SIZE = 16
+ ACCEL_GRID_SIZE = 16,
+ QUANTIZATION_MAX = 1 << 16 // A power of two smaller than the 23 bit significand of a float.
};
struct GridPos {
Vector3i pos;
@@ -173,38 +174,25 @@ class Delaunay3D {
R128 radius2 = rel2_x * rel2_x + rel2_y * rel2_y + rel2_z * rel2_z;
- return radius2 < (p_simplex.circum_r2 - R128(0.00001));
+ return radius2 < (p_simplex.circum_r2 - R128(0.0000000001));
+ // When this tolerance is too big, it can result in overlapping simplices.
+ // When it's too small, large amounts of planar simplices are created.
}
static bool simplex_is_coplanar(const Vector3 *p_points, const Simplex &p_simplex) {
- Plane p(p_points[p_simplex.points[0]], p_points[p_simplex.points[1]], p_points[p_simplex.points[2]]);
- if (ABS(p.distance_to(p_points[p_simplex.points[3]])) < CMP_EPSILON) {
- return true;
+ // Checking every possible distance like this is overkill, but only checking
+ // one is not enough. If the simplex is almost planar then the vectors p1-p2
+ // and p1-p3 can be practically collinear, which makes Plane unreliable.
+ for (uint32_t i = 0; i < 4; i++) {
+ Plane p(p_points[p_simplex.points[i]], p_points[p_simplex.points[(i + 1) % 4]], p_points[p_simplex.points[(i + 2) % 4]]);
+ // This tolerance should not be smaller than the one used with
+ // Plane::has_point() when creating the LightmapGI probe BSP tree.
+ if (ABS(p.distance_to(p_points[p_simplex.points[(i + 3) % 4]])) < 0.001) {
+ return true;
+ }
}
- Projection cm;
-
- cm.columns[0][0] = p_points[p_simplex.points[0]].x;
- cm.columns[0][1] = p_points[p_simplex.points[1]].x;
- cm.columns[0][2] = p_points[p_simplex.points[2]].x;
- cm.columns[0][3] = p_points[p_simplex.points[3]].x;
-
- cm.columns[1][0] = p_points[p_simplex.points[0]].y;
- cm.columns[1][1] = p_points[p_simplex.points[1]].y;
- cm.columns[1][2] = p_points[p_simplex.points[2]].y;
- cm.columns[1][3] = p_points[p_simplex.points[3]].y;
-
- cm.columns[2][0] = p_points[p_simplex.points[0]].z;
- cm.columns[2][1] = p_points[p_simplex.points[1]].z;
- cm.columns[2][2] = p_points[p_simplex.points[2]].z;
- cm.columns[2][3] = p_points[p_simplex.points[3]].z;
-
- cm.columns[3][0] = 1.0;
- cm.columns[3][1] = 1.0;
- cm.columns[3][2] = 1.0;
- cm.columns[3][3] = 1.0;
-
- return ABS(cm.determinant()) <= CMP_EPSILON;
+ return false;
}
public:
@@ -215,9 +203,10 @@ public:
static Vector<OutputSimplex> tetrahedralize(const Vector<Vector3> &p_points) {
uint32_t point_count = p_points.size();
Vector3 *points = (Vector3 *)memalloc(sizeof(Vector3) * (point_count + 4));
+ const Vector3 *src_points = p_points.ptr();
+ Vector3 proportions;
{
- const Vector3 *src_points = p_points.ptr();
AABB rect;
for (uint32_t i = 0; i < point_count; i++) {
Vector3 point = src_points[i];
@@ -226,17 +215,25 @@ public:
} else {
rect.expand_to(point);
}
- points[i] = point;
}
+ real_t longest_axis = rect.size[rect.get_longest_axis_index()];
+ proportions = Vector3(longest_axis, longest_axis, longest_axis) / rect.size;
+
for (uint32_t i = 0; i < point_count; i++) {
- points[i] = (points[i] - rect.position) / rect.size;
+ // Scale points to the unit cube to better utilize R128 precision
+ // and quantize to stabilize triangulation over a wide range of
+ // distances.
+ points[i] = Vector3(Vector3i((src_points[i] - rect.position) / longest_axis * QUANTIZATION_MAX)) / QUANTIZATION_MAX;
}
- const real_t delta_max = Math::sqrt(2.0) * 20.0;
+ const real_t delta_max = Math::sqrt(2.0) * 100.0;
Vector3 center = Vector3(0.5, 0.5, 0.5);
- // any simplex that contains everything is good
+ // The larger the root simplex is, the more likely it is that the
+ // triangulation is convex. If it's not absolutely huge, there can
+ // be missing simplices that are not created for the outermost faces
+ // of the point cloud if the point density is very low there.
points[point_count + 0] = center + Vector3(0, 1, 0) * delta_max;
points[point_count + 1] = center + Vector3(0, -1, 1) * delta_max;
points[point_count + 2] = center + Vector3(1, -1, -1) * delta_max;
@@ -271,7 +268,7 @@ public:
for (uint32_t i = 0; i < point_count; i++) {
bool unique = true;
for (uint32_t j = i + 1; j < point_count; j++) {
- if (points[i].is_equal_approx(points[j])) {
+ if (points[i] == points[j]) {
unique = false;
break;
}
@@ -280,8 +277,8 @@ public:
continue;
}
- Vector3i grid_pos = Vector3i(points[i] * ACCEL_GRID_SIZE);
- grid_pos = grid_pos.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
+ Vector3i grid_pos = Vector3i(points[i] * proportions * ACCEL_GRID_SIZE);
+ grid_pos = grid_pos.clampi(0, ACCEL_GRID_SIZE - 1);
for (List<Simplex *>::Element *E = acceleration_grid[grid_pos.x][grid_pos.y][grid_pos.z].front(); E;) {
List<Simplex *>::Element *N = E->next(); //may be deleted
@@ -300,6 +297,9 @@ public:
Triangle t = Triangle(simplex->points[triangle_order[k][0]], simplex->points[triangle_order[k][1]], simplex->points[triangle_order[k][2]]);
uint32_t *p = triangles_inserted.lookup_ptr(t);
if (p) {
+ // This Delaunay implementation uses the Bowyer-Watson algorithm.
+ // The rule is that you don't reuse any triangles that were
+ // shared by any of the retriangulated simplices.
triangles[*p].bad = true;
} else {
triangles_inserted.insert(t, triangles.size());
@@ -307,7 +307,6 @@ public:
}
}
- //remove simplex and continue
simplex_list.erase(simplex->SE);
for (const GridPos &gp : simplex->grid_positions) {
@@ -334,10 +333,10 @@ public:
const real_t radius2 = Math::sqrt(double(new_simplex->circum_r2)) + 0.0001;
Vector3 extents = Vector3(radius2, radius2, radius2);
- Vector3i from = Vector3i((center - extents) * ACCEL_GRID_SIZE);
- Vector3i to = Vector3i((center + extents) * ACCEL_GRID_SIZE);
- from = from.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
- to = to.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
+ Vector3i from = Vector3i((center - extents) * proportions * ACCEL_GRID_SIZE);
+ Vector3i to = Vector3i((center + extents) * proportions * ACCEL_GRID_SIZE);
+ from = from.clampi(0, ACCEL_GRID_SIZE - 1);
+ to = to.clampi(0, ACCEL_GRID_SIZE - 1);
for (int32_t x = from.x; x <= to.x; x++) {
for (int32_t y = from.y; y <= to.y; y++) {
@@ -370,7 +369,7 @@ public:
break;
}
}
- if (invalid || simplex_is_coplanar(points, *simplex)) {
+ if (invalid || simplex_is_coplanar(src_points, *simplex)) {
memdelete(simplex);
continue;
}
diff --git a/core/math/face3.h b/core/math/face3.h
index 3dd47d0226..519dcb6414 100644
--- a/core/math/face3.h
+++ b/core/math/face3.h
@@ -36,7 +36,7 @@
#include "core/math/transform_3d.h"
#include "core/math/vector3.h"
-struct _NO_DISCARD_ Face3 {
+struct [[nodiscard]] Face3 {
enum Side {
SIDE_OVER,
SIDE_UNDER,
diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp
index 602e95bc13..d60619b27f 100644
--- a/core/math/geometry_2d.cpp
+++ b/core/math/geometry_2d.cpp
@@ -30,12 +30,12 @@
#include "geometry_2d.h"
-#include "thirdparty/misc/clipper.hpp"
+#include "thirdparty/clipper2/include/clipper2/clipper.h"
#include "thirdparty/misc/polypartition.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "thirdparty/misc/stb_rect_pack.h"
-#define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON.
+#define PRECISION 5 // Based on CMP_EPSILON.
Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) {
Vector<Vector<Vector2>> decomp;
@@ -196,58 +196,59 @@ void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_re
}
Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
- using namespace ClipperLib;
+ using namespace Clipper2Lib;
- ClipType op = ctUnion;
+ ClipType op = ClipType::Union;
switch (p_op) {
case OPERATION_UNION:
- op = ctUnion;
+ op = ClipType::Union;
break;
case OPERATION_DIFFERENCE:
- op = ctDifference;
+ op = ClipType::Difference;
break;
case OPERATION_INTERSECTION:
- op = ctIntersection;
+ op = ClipType::Intersection;
break;
case OPERATION_XOR:
- op = ctXor;
+ op = ClipType::Xor;
break;
}
- Path path_a, path_b;
- // Need to scale points (Clipper's requirement for robust computation).
+ PathD path_a(p_polypath_a.size());
for (int i = 0; i != p_polypath_a.size(); ++i) {
- path_a << IntPoint(p_polypath_a[i].x * (real_t)SCALE_FACTOR, p_polypath_a[i].y * (real_t)SCALE_FACTOR);
+ path_a[i] = PointD(p_polypath_a[i].x, p_polypath_a[i].y);
}
+ PathD path_b(p_polypath_b.size());
for (int i = 0; i != p_polypath_b.size(); ++i) {
- path_b << IntPoint(p_polypath_b[i].x * (real_t)SCALE_FACTOR, p_polypath_b[i].y * (real_t)SCALE_FACTOR);
+ path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y);
}
- Clipper clp;
- clp.AddPath(path_a, ptSubject, !is_a_open); // Forward compatible with Clipper 10.0.0.
- clp.AddPath(path_b, ptClip, true); // Polylines cannot be set as clip.
- Paths paths;
+ ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision.
+ clp.PreserveCollinear(false); // Remove redundant vertices.
+ if (is_a_open) {
+ clp.AddOpenSubject({ path_a });
+ } else {
+ clp.AddSubject({ path_a });
+ }
+ clp.AddClip({ path_b });
+
+ PathsD paths;
if (is_a_open) {
- PolyTree tree; // Needed to populate polylines.
- clp.Execute(op, tree);
- OpenPathsFromPolyTree(tree, paths);
+ PolyTreeD tree; // Needed to populate polylines.
+ clp.Execute(op, FillRule::EvenOdd, tree, paths);
} else {
- clp.Execute(op, paths); // Works on closed polygons only.
+ clp.Execute(op, FillRule::EvenOdd, paths); // Works on closed polygons only.
}
- // Have to scale points down now.
+
Vector<Vector<Point2>> polypaths;
+ for (PathsD::size_type i = 0; i < paths.size(); ++i) {
+ const PathD &path = paths[i];
- for (Paths::size_type i = 0; i < paths.size(); ++i) {
Vector<Vector2> polypath;
-
- const Path &scaled_path = paths[i];
-
- for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
- polypath.push_back(Point2(
- static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
- static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
+ for (PathsD::size_type j = 0; j < path.size(); ++j) {
+ polypath.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
polypaths.push_back(polypath);
}
@@ -255,67 +256,61 @@ Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation
}
Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
- using namespace ClipperLib;
+ using namespace Clipper2Lib;
- JoinType jt = jtSquare;
+ JoinType jt = JoinType::Square;
switch (p_join_type) {
case JOIN_SQUARE:
- jt = jtSquare;
+ jt = JoinType::Square;
break;
case JOIN_ROUND:
- jt = jtRound;
+ jt = JoinType::Round;
break;
case JOIN_MITER:
- jt = jtMiter;
+ jt = JoinType::Miter;
break;
}
- EndType et = etClosedPolygon;
+ EndType et = EndType::Polygon;
switch (p_end_type) {
case END_POLYGON:
- et = etClosedPolygon;
+ et = EndType::Polygon;
break;
case END_JOINED:
- et = etClosedLine;
+ et = EndType::Joined;
break;
case END_BUTT:
- et = etOpenButt;
+ et = EndType::Butt;
break;
case END_SQUARE:
- et = etOpenSquare;
+ et = EndType::Square;
break;
case END_ROUND:
- et = etOpenRound;
+ et = EndType::Round;
break;
}
- ClipperOffset co(2.0, 0.25f * (real_t)SCALE_FACTOR); // Defaults from ClipperOffset.
- Path path;
- // Need to scale points (Clipper's requirement for robust computation).
+ PathD polypath(p_polypath.size());
for (int i = 0; i != p_polypath.size(); ++i) {
- path << IntPoint(p_polypath[i].x * (real_t)SCALE_FACTOR, p_polypath[i].y * (real_t)SCALE_FACTOR);
+ polypath[i] = PointD(p_polypath[i].x, p_polypath[i].y);
}
- co.AddPath(path, jt, et);
- Paths paths;
- co.Execute(paths, p_delta * (real_t)SCALE_FACTOR); // Inflate/deflate.
+ // Inflate/deflate.
+ PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0);
+ // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
+ // and the PRECISION is used to scale points up internally, to attain the desired precision.
- // Have to scale points down now.
Vector<Vector<Point2>> polypaths;
+ for (PathsD::size_type i = 0; i < paths.size(); ++i) {
+ const PathD &path = paths[i];
- for (Paths::size_type i = 0; i < paths.size(); ++i) {
- Vector<Vector2> polypath;
-
- const Path &scaled_path = paths[i];
-
- for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
- polypath.push_back(Point2(
- static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
- static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
+ Vector<Vector2> polypath2;
+ for (PathsD::size_type j = 0; j < path.size(); ++j) {
+ polypath2.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
- polypaths.push_back(polypath);
+ polypaths.push_back(polypath2);
}
return polypaths;
}
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 1502b2807c..83ebdc5a84 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -350,6 +350,8 @@ public:
return triangles;
}
+ // Assumes cartesian coordinate system with +x to the right, +y up.
+ // If using screen coordinates (+x to the right, +y down) the result will need to be flipped.
static bool is_polygon_clockwise(const Vector<Vector2> &p_polygon) {
int c = p_polygon.size();
if (c < 3) {
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 3060f31970..fd53ed28fd 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -447,14 +447,22 @@ public:
static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_s) {
if (is_equal_approx(p_from, p_to)) {
- return p_from;
+ if (likely(p_from <= p_to)) {
+ return p_s <= p_from ? 0.0 : 1.0;
+ } else {
+ return p_s <= p_to ? 1.0 : 0.0;
+ }
}
double s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0, 1.0);
return s * s * (3.0 - 2.0 * s);
}
static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_s) {
if (is_equal_approx(p_from, p_to)) {
- return p_from;
+ if (likely(p_from <= p_to)) {
+ return p_s <= p_from ? 0.0f : 1.0f;
+ } else {
+ return p_s <= p_to ? 1.0f : 0.0f;
+ }
}
float s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0f, 1.0f);
return s * s * (3.0f - 2.0f * s);
diff --git a/core/math/plane.h b/core/math/plane.h
index 8159f25342..6529fea60a 100644
--- a/core/math/plane.h
+++ b/core/math/plane.h
@@ -35,7 +35,7 @@
class Variant;
-struct _NO_DISCARD_ Plane {
+struct [[nodiscard]] Plane {
Vector3 normal;
real_t d = 0;
diff --git a/core/math/projection.h b/core/math/projection.h
index f3ed9d7b1c..5af43561c0 100644
--- a/core/math/projection.h
+++ b/core/math/projection.h
@@ -43,7 +43,7 @@ struct Rect2;
struct Transform3D;
struct Vector2;
-struct _NO_DISCARD_ Projection {
+struct [[nodiscard]] Projection {
enum Planes {
PLANE_NEAR,
PLANE_FAR,
diff --git a/core/math/quaternion.h b/core/math/quaternion.h
index 868a2916f5..655e55e0a2 100644
--- a/core/math/quaternion.h
+++ b/core/math/quaternion.h
@@ -35,7 +35,7 @@
#include "core/math/vector3.h"
#include "core/string/ustring.h"
-struct _NO_DISCARD_ Quaternion {
+struct [[nodiscard]] Quaternion {
union {
struct {
real_t x;
diff --git a/core/math/quick_hull.cpp b/core/math/quick_hull.cpp
index 4483f61bc4..6a60a5925d 100644
--- a/core/math/quick_hull.cpp
+++ b/core/math/quick_hull.cpp
@@ -55,7 +55,7 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry3D::MeshData &r_
HashSet<Vector3> valid_cache;
for (int i = 0; i < p_points.size(); i++) {
- Vector3 sp = p_points[i].snapped(Vector3(0.0001, 0.0001, 0.0001));
+ Vector3 sp = p_points[i].snappedf(0.0001);
if (valid_cache.has(sp)) {
valid_points.write[i] = false;
} else {
diff --git a/core/math/rect2.h b/core/math/rect2.h
index 7f410feb1c..9cb341b689 100644
--- a/core/math/rect2.h
+++ b/core/math/rect2.h
@@ -38,7 +38,7 @@ class String;
struct Rect2i;
struct Transform2D;
-struct _NO_DISCARD_ Rect2 {
+struct [[nodiscard]] Rect2 {
Point2 position;
Size2 size;
@@ -278,7 +278,7 @@ struct _NO_DISCARD_ Rect2 {
}
_FORCE_INLINE_ Rect2 abs() const {
- return Rect2(position + size.min(Point2()), size.abs());
+ return Rect2(position + size.minf(0), size.abs());
}
_FORCE_INLINE_ Rect2 round() const {
diff --git a/core/math/rect2i.h b/core/math/rect2i.h
index 64806414c7..5f3a3d54f5 100644
--- a/core/math/rect2i.h
+++ b/core/math/rect2i.h
@@ -37,7 +37,7 @@
class String;
struct Rect2;
-struct _NO_DISCARD_ Rect2i {
+struct [[nodiscard]] Rect2i {
Point2i position;
Size2i size;
@@ -213,7 +213,7 @@ struct _NO_DISCARD_ Rect2i {
}
_FORCE_INLINE_ Rect2i abs() const {
- return Rect2i(position + size.min(Point2i()), size.abs());
+ return Rect2i(position + size.mini(0), size.abs());
}
_FORCE_INLINE_ void set_end(const Vector2i &p_end) {
diff --git a/core/math/static_raycaster.h b/core/math/static_raycaster.h
index c53868e12d..74e4b75163 100644
--- a/core/math/static_raycaster.h
+++ b/core/math/static_raycaster.h
@@ -49,7 +49,7 @@ protected:
static StaticRaycaster *(*create_function)();
public:
- // compatible with embree3 rays
+ // Compatible with embree4 rays.
struct __aligned(16) Ray {
const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h
diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h
index 4ec2dc119c..476577508f 100644
--- a/core/math/transform_2d.h
+++ b/core/math/transform_2d.h
@@ -38,7 +38,7 @@
class String;
-struct _NO_DISCARD_ Transform2D {
+struct [[nodiscard]] Transform2D {
// Warning #1: basis of Transform2D is stored differently from Basis. In terms of columns array, the basis matrix looks like "on paper":
// M = (columns[0][0] columns[1][0])
// (columns[0][1] columns[1][1])
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index 7d89b86c75..b1de233445 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -36,7 +36,7 @@
#include "core/math/plane.h"
#include "core/templates/vector.h"
-struct _NO_DISCARD_ Transform3D {
+struct [[nodiscard]] Transform3D {
Basis basis;
Vector3 origin;
diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp
index 7cfe880b5a..1cd35b3d1a 100644
--- a/core/math/transform_interpolator.cpp
+++ b/core/math/transform_interpolator.cpp
@@ -31,46 +31,354 @@
#include "transform_interpolator.h"
#include "core/math/transform_2d.h"
+#include "core/math/transform_3d.h"
void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
- // Extract parameters.
- Vector2 p1 = p_prev.get_origin();
- Vector2 p2 = p_curr.get_origin();
-
// Special case for physics interpolation, if flipping, don't interpolate basis.
// If the determinant polarity changes, the handedness of the coordinate system changes.
if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) {
r_result.columns[0] = p_curr.columns[0];
r_result.columns[1] = p_curr.columns[1];
- r_result.set_origin(p1.lerp(p2, p_fraction));
+ r_result.set_origin(p_prev.get_origin().lerp(p_curr.get_origin(), p_fraction));
return;
}
- real_t r1 = p_prev.get_rotation();
- real_t r2 = p_curr.get_rotation();
+ r_result = p_prev.interpolate_with(p_curr, p_fraction);
+}
+
+void TransformInterpolator::interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction) {
+ r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction);
+ interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction);
+}
+
+void TransformInterpolator::interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) {
+ Method method = find_method(p_prev, p_curr);
+ interpolate_basis_via_method(p_prev, p_curr, r_result, p_fraction, method);
+}
+
+void TransformInterpolator::interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method) {
+ r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction);
+ interpolate_basis_via_method(p_prev.basis, p_curr.basis, r_result.basis, p_fraction, p_method);
+}
+
+void TransformInterpolator::interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method) {
+ switch (p_method) {
+ default: {
+ interpolate_basis_linear(p_prev, p_curr, r_result, p_fraction);
+ } break;
+ case INTERP_SLERP: {
+ r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction);
+ } break;
+ case INTERP_SCALED_SLERP: {
+ interpolate_basis_scaled_slerp(p_prev, p_curr, r_result, p_fraction);
+ } break;
+ }
+}
+
+Quaternion TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) {
+ Basis m = p_basis;
+ real_t trace = m.rows[0][0] + m.rows[1][1] + m.rows[2][2];
+ real_t temp[4];
+
+ if (trace > 0.0) {
+ real_t s = Math::sqrt(trace + 1.0f);
+ temp[3] = (s * 0.5f);
+ s = 0.5f / s;
+
+ temp[0] = ((m.rows[2][1] - m.rows[1][2]) * s);
+ temp[1] = ((m.rows[0][2] - m.rows[2][0]) * s);
+ temp[2] = ((m.rows[1][0] - m.rows[0][1]) * s);
+ } else {
+ int i = m.rows[0][0] < m.rows[1][1]
+ ? (m.rows[1][1] < m.rows[2][2] ? 2 : 1)
+ : (m.rows[0][0] < m.rows[2][2] ? 2 : 0);
+ int j = (i + 1) % 3;
+ int k = (i + 2) % 3;
- Size2 s1 = p_prev.get_scale();
- Size2 s2 = p_curr.get_scale();
+ real_t s = Math::sqrt(m.rows[i][i] - m.rows[j][j] - m.rows[k][k] + 1.0f);
+ temp[i] = s * 0.5f;
+ s = 0.5f / s;
- // Slerp rotation.
- Vector2 v1(Math::cos(r1), Math::sin(r1));
- Vector2 v2(Math::cos(r2), Math::sin(r2));
+ temp[3] = (m.rows[k][j] - m.rows[j][k]) * s;
+ temp[j] = (m.rows[j][i] + m.rows[i][j]) * s;
+ temp[k] = (m.rows[k][i] + m.rows[i][k]) * s;
+ }
- real_t dot = v1.dot(v2);
+ return Quaternion(temp[0], temp[1], temp[2], temp[3]);
+}
- dot = CLAMP(dot, -1, 1);
+Quaternion TransformInterpolator::_quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction) {
+ Quaternion to1;
+ real_t omega, cosom, sinom, scale0, scale1;
- Vector2 v;
+ // Calculate cosine.
+ cosom = p_from.dot(p_to);
- if (dot > 0.9995f) {
- v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues.
+ // Adjust signs (if necessary)
+ if (cosom < 0.0f) {
+ cosom = -cosom;
+ to1.x = -p_to.x;
+ to1.y = -p_to.y;
+ to1.z = -p_to.z;
+ to1.w = -p_to.w;
} else {
- real_t angle = p_fraction * Math::acos(dot);
- Vector2 v3 = (v2 - v1 * dot).normalized();
- v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
+ to1.x = p_to.x;
+ to1.y = p_to.y;
+ to1.z = p_to.z;
+ to1.w = p_to.w;
+ }
+
+ // Calculate coefficients.
+
+ // This check could possibly be removed as we dealt with this
+ // case in the find_method() function, but is left for safety, it probably
+ // isn't a bottleneck.
+ if ((1.0f - cosom) > (real_t)CMP_EPSILON) {
+ // standard case (slerp)
+ omega = Math::acos(cosom);
+ sinom = Math::sin(omega);
+ scale0 = Math::sin((1.0f - p_fraction) * omega) / sinom;
+ scale1 = Math::sin(p_fraction * omega) / sinom;
+ } else {
+ // "from" and "to" quaternions are very close
+ // ... so we can do a linear interpolation
+ scale0 = 1.0f - p_fraction;
+ scale1 = p_fraction;
+ }
+ // Calculate final values.
+ return Quaternion(
+ scale0 * p_from.x + scale1 * to1.x,
+ scale0 * p_from.y + scale1 * to1.y,
+ scale0 * p_from.z + scale1 * to1.z,
+ scale0 * p_from.w + scale1 * to1.w);
+}
+
+Basis TransformInterpolator::_basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction) {
+ Quaternion from = _basis_to_quat_unchecked(p_from);
+ Quaternion to = _basis_to_quat_unchecked(p_to);
+
+ Basis b(_quat_slerp_unchecked(from, to, p_fraction));
+ return b;
+}
+
+void TransformInterpolator::interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction) {
+ // Normalize both and find lengths.
+ Vector3 lengths_prev = _basis_orthonormalize(p_prev);
+ Vector3 lengths_curr = _basis_orthonormalize(p_curr);
+
+ r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction);
+
+ // Now the result is unit length basis, we need to scale.
+ Vector3 lengths_lerped = lengths_prev + ((lengths_curr - lengths_prev) * p_fraction);
+
+ // Keep a note that the column / row order of the basis is weird,
+ // so keep an eye for bugs with this.
+ r_result[0] *= lengths_lerped;
+ r_result[1] *= lengths_lerped;
+ r_result[2] *= lengths_lerped;
+}
+
+void TransformInterpolator::interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) {
+ // Interpolate basis.
+ r_result = p_prev.lerp(p_curr, p_fraction);
+
+ // It turns out we need to guard against zero scale basis.
+ // This is kind of silly, as we should probably fix the bugs elsewhere in Godot that can't deal with
+ // zero scale, but until that time...
+ for (int n = 0; n < 3; n++) {
+ Vector3 &axis = r_result[n];
+
+ // Not ok, this could cause errors due to bugs elsewhere,
+ // so we will bodge set this to a small value.
+ const real_t smallest = 0.0001f;
+ const real_t smallest_squared = smallest * smallest;
+ if (axis.length_squared() < smallest_squared) {
+ // Setting a different component to the smallest
+ // helps prevent the situation where all the axes are pointing in the same direction,
+ // which could be a problem for e.g. cross products...
+ axis[n] = smallest;
+ }
+ }
+}
+
+// Returns length.
+real_t TransformInterpolator::_vec3_normalize(Vector3 &p_vec) {
+ real_t lengthsq = p_vec.length_squared();
+ if (lengthsq == 0.0f) {
+ p_vec.x = p_vec.y = p_vec.z = 0.0f;
+ return 0.0f;
+ }
+ real_t length = Math::sqrt(lengthsq);
+ p_vec.x /= length;
+ p_vec.y /= length;
+ p_vec.z /= length;
+ return length;
+}
+
+// Returns lengths.
+Vector3 TransformInterpolator::_basis_orthonormalize(Basis &r_basis) {
+ // Gram-Schmidt Process.
+
+ Vector3 x = r_basis.get_column(0);
+ Vector3 y = r_basis.get_column(1);
+ Vector3 z = r_basis.get_column(2);
+
+ Vector3 lengths;
+
+ lengths.x = _vec3_normalize(x);
+ y = (y - x * (x.dot(y)));
+ lengths.y = _vec3_normalize(y);
+ z = (z - x * (x.dot(z)) - y * (y.dot(z)));
+ lengths.z = _vec3_normalize(z);
+
+ r_basis.set_column(0, x);
+ r_basis.set_column(1, y);
+ r_basis.set_column(2, z);
+
+ return lengths;
+}
+
+TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat) {
+ // Axis lengths.
+ Vector3 al = Vector3(p_basis.get_column(0).length_squared(),
+ p_basis.get_column(1).length_squared(),
+ p_basis.get_column(2).length_squared());
+
+ // Non unit scale?
+ if (r_needed_normalize || !_vec3_is_equal_approx(al, Vector3(1.0, 1.0, 1.0), (real_t)0.001f)) {
+ // If the basis is not normalized (at least approximately), it will fail the checks needed for slerp.
+ // So we try to detect a scaled (but not sheared) basis, which we *can* slerp by normalizing first,
+ // and lerping the scales separately.
+
+ // If any of the axes are really small, it is unlikely to be a valid rotation, or is scaled too small to deal with float error.
+ const real_t sl_epsilon = 0.00001f;
+ if ((al.x < sl_epsilon) ||
+ (al.y < sl_epsilon) ||
+ (al.z < sl_epsilon)) {
+ return INTERP_LERP;
+ }
+
+ // Normalize the basis.
+ Basis norm_basis = p_basis;
+
+ al.x = Math::sqrt(al.x);
+ al.y = Math::sqrt(al.y);
+ al.z = Math::sqrt(al.z);
+
+ norm_basis.set_column(0, norm_basis.get_column(0) / al.x);
+ norm_basis.set_column(1, norm_basis.get_column(1) / al.y);
+ norm_basis.set_column(2, norm_basis.get_column(2) / al.z);
+
+ // This doesn't appear necessary, as the later checks will catch it.
+ // if (!_basis_is_orthogonal_any_scale(norm_basis)) {
+ // return INTERP_LERP;
+ // }
+
+ p_basis = norm_basis;
+
+ // Orthonormalize not necessary as normal normalization(!) works if the
+ // axes are orthonormal.
+ // p_basis.orthonormalize();
+
+ // If we needed to normalize one of the two bases, we will need to normalize both,
+ // regardless of whether the 2nd needs it, just to make sure it takes the path to return
+ // INTERP_SCALED_LERP on the 2nd call of _test_basis.
+ r_needed_normalize = true;
+ }
+
+ // Apply less stringent tests than the built in slerp, the standard Godot slerp
+ // is too susceptible to float error to be useful.
+ real_t det = p_basis.determinant();
+ if (!Math::is_equal_approx(det, 1, (real_t)0.01f)) {
+ return INTERP_LERP;
+ }
+
+ if (!_basis_is_orthogonal(p_basis)) {
+ return INTERP_LERP;
+ }
+
+ // TODO: This could possibly be less stringent too, check this.
+ r_quat = _basis_to_quat_unchecked(p_basis);
+ if (!r_quat.is_normalized()) {
+ return INTERP_LERP;
+ }
+
+ return r_needed_normalize ? INTERP_SCALED_SLERP : INTERP_SLERP;
+}
+
+// This check doesn't seem to be needed but is preserved in case of bugs.
+bool TransformInterpolator::_basis_is_orthogonal_any_scale(const Basis &p_basis) {
+ Vector3 cross = p_basis.get_column(0).cross(p_basis.get_column(1));
+ real_t l = _vec3_normalize(cross);
+ // Too small numbers, revert to lerp.
+ if (l < 0.001f) {
+ return false;
+ }
+
+ const real_t epsilon = 0.9995f;
+
+ real_t dot = cross.dot(p_basis.get_column(2));
+ if (dot < epsilon) {
+ return false;
+ }
+
+ cross = p_basis.get_column(1).cross(p_basis.get_column(2));
+ l = _vec3_normalize(cross);
+ // Too small numbers, revert to lerp.
+ if (l < 0.001f) {
+ return false;
+ }
+
+ dot = cross.dot(p_basis.get_column(0));
+ if (dot < epsilon) {
+ return false;
+ }
+
+ return true;
+}
+
+bool TransformInterpolator::_basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon) {
+ Basis identity;
+ Basis m = p_basis * p_basis.transposed();
+
+ // Less stringent tests than the standard Godot slerp.
+ if (!_vec3_is_equal_approx(m[0], identity[0], p_epsilon) || !_vec3_is_equal_approx(m[1], identity[1], p_epsilon) || !_vec3_is_equal_approx(m[2], identity[2], p_epsilon)) {
+ return false;
+ }
+ return true;
+}
+
+real_t TransformInterpolator::checksum_transform_3d(const Transform3D &p_transform) {
+ // just a really basic checksum, this can probably be improved
+ real_t sum = _vec3_sum(p_transform.origin);
+ sum -= _vec3_sum(p_transform.basis.rows[0]);
+ sum += _vec3_sum(p_transform.basis.rows[1]);
+ sum -= _vec3_sum(p_transform.basis.rows[2]);
+ return sum;
+}
+
+TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) {
+ bool needed_normalize = false;
+
+ Quaternion q0;
+ Method method = _test_basis(p_a, needed_normalize, q0);
+ if (method == INTERP_LERP) {
+ return method;
+ }
+
+ Quaternion q1;
+ method = _test_basis(p_b, needed_normalize, q1);
+ if (method == INTERP_LERP) {
+ return method;
+ }
+
+ // Are they close together?
+ // Apply the same test that will revert to lerp as is present in the slerp routine.
+ // Calculate cosine.
+ real_t cosom = Math::abs(q0.dot(q1));
+ if ((1.0f - cosom) <= (real_t)CMP_EPSILON) {
+ return INTERP_LERP;
}
- // Construct matrix.
- r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction));
- r_result.scale_basis(s1.lerp(s2, p_fraction));
+ return method;
}
diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h
index a9bce2bd7f..cc556707e4 100644
--- a/core/math/transform_interpolator.h
+++ b/core/math/transform_interpolator.h
@@ -32,15 +32,64 @@
#define TRANSFORM_INTERPOLATOR_H
#include "core/math/math_defs.h"
+#include "core/math/vector3.h"
+
+// Keep all the functions for fixed timestep interpolation together.
+// There are two stages involved:
+// Finding a method, for determining the interpolation method between two
+// keyframes (which are physics ticks).
+// And applying that pre-determined method.
+
+// Pre-determining the method makes sense because it is expensive and often
+// several frames may occur between each physics tick, which will make it cheaper
+// than performing every frame.
struct Transform2D;
+struct Transform3D;
+struct Basis;
+struct Quaternion;
class TransformInterpolator {
+public:
+ enum Method {
+ INTERP_LERP,
+ INTERP_SLERP,
+ INTERP_SCALED_SLERP,
+ };
+
private:
- static bool _sign(real_t p_val) { return p_val >= 0; }
+ _FORCE_INLINE_ static bool _sign(real_t p_val) { return p_val >= 0; }
+ static real_t _vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; }
+ static real_t _vec3_normalize(Vector3 &p_vec);
+ _FORCE_INLINE_ static bool _vec3_is_equal_approx(const Vector3 &p_a, const Vector3 &p_b, real_t p_tolerance) {
+ return Math::is_equal_approx(p_a.x, p_b.x, p_tolerance) && Math::is_equal_approx(p_a.y, p_b.y, p_tolerance) && Math::is_equal_approx(p_a.z, p_b.z, p_tolerance);
+ }
+ static Vector3 _basis_orthonormalize(Basis &r_basis);
+ static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat);
+ static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction);
+ static Quaternion _quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction);
+ static Quaternion _basis_to_quat_unchecked(const Basis &p_basis);
+ static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f);
+ static bool _basis_is_orthogonal_any_scale(const Basis &p_basis);
+
+ static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction);
+ static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction);
public:
static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
+
+ // Generic functions, use when you don't know what method should be used, e.g. from GDScript.
+ // These will be slower.
+ static void interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction);
+ static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction);
+
+ // Optimized function when you know ahead of time the method.
+ static void interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method);
+ static void interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method);
+
+ static real_t checksum_transform_3d(const Transform3D &p_transform);
+
+ static Method find_method(const Basis &p_a, const Basis &p_b);
};
#endif // TRANSFORM_INTERPOLATOR_H
diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp
index 0da1b8c7ad..01b733183d 100644
--- a/core/math/triangle_mesh.cpp
+++ b/core/math/triangle_mesh.cpp
@@ -133,7 +133,7 @@ void TriangleMesh::create(const Vector<Vector3> &p_faces, const Vector<int32_t>
for (int j = 0; j < 3; j++) {
int vidx = -1;
- Vector3 vs = v[j].snapped(Vector3(0.0001, 0.0001, 0.0001));
+ Vector3 vs = v[j].snappedf(0.0001);
HashMap<Vector3, int>::Iterator E = db.find(vs);
if (E) {
vidx = E->value;
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index 198fd85d20..e86b97d6a8 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -135,12 +135,24 @@ Vector2 Vector2::clamp(const Vector2 &p_min, const Vector2 &p_max) const {
CLAMP(y, p_min.y, p_max.y));
}
+Vector2 Vector2::clampf(real_t p_min, real_t p_max) const {
+ return Vector2(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max));
+}
+
Vector2 Vector2::snapped(const Vector2 &p_step) const {
return Vector2(
Math::snapped(x, p_step.x),
Math::snapped(y, p_step.y));
}
+Vector2 Vector2::snappedf(real_t p_step) const {
+ return Vector2(
+ Math::snapped(x, p_step),
+ Math::snapped(y, p_step));
+}
+
Vector2 Vector2::limit_length(real_t p_len) const {
const real_t l = length();
Vector2 v = *this;
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 6ad003edd1..edb47db6fd 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -37,7 +37,7 @@
class String;
struct Vector2i;
-struct _NO_DISCARD_ Vector2 {
+struct [[nodiscard]] Vector2 {
static const int AXIS_COUNT = 2;
enum Axis {
@@ -89,10 +89,18 @@ struct _NO_DISCARD_ Vector2 {
return Vector2(MIN(x, p_vector2.x), MIN(y, p_vector2.y));
}
+ Vector2 minf(real_t p_scalar) const {
+ return Vector2(MIN(x, p_scalar), MIN(y, p_scalar));
+ }
+
Vector2 max(const Vector2 &p_vector2) const {
return Vector2(MAX(x, p_vector2.x), MAX(y, p_vector2.y));
}
+ Vector2 maxf(real_t p_scalar) const {
+ return Vector2(MAX(x, p_scalar), MAX(y, p_scalar));
+ }
+
real_t distance_to(const Vector2 &p_vector2) const;
real_t distance_squared_to(const Vector2 &p_vector2) const;
real_t angle_to(const Vector2 &p_vector2) const;
@@ -168,7 +176,9 @@ struct _NO_DISCARD_ Vector2 {
Vector2 ceil() const;
Vector2 round() const;
Vector2 snapped(const Vector2 &p_by) const;
+ Vector2 snappedf(real_t p_by) const;
Vector2 clamp(const Vector2 &p_min, const Vector2 &p_max) const;
+ Vector2 clampf(real_t p_min, real_t p_max) const;
real_t aspect() const { return width / height; }
operator String() const;
diff --git a/core/math/vector2i.cpp b/core/math/vector2i.cpp
index ba79d439dd..790f564734 100644
--- a/core/math/vector2i.cpp
+++ b/core/math/vector2i.cpp
@@ -39,12 +39,24 @@ Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const {
CLAMP(y, p_min.y, p_max.y));
}
+Vector2i Vector2i::clampi(int32_t p_min, int32_t p_max) const {
+ return Vector2i(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max));
+}
+
Vector2i Vector2i::snapped(const Vector2i &p_step) const {
return Vector2i(
Math::snapped(x, p_step.x),
Math::snapped(y, p_step.y));
}
+Vector2i Vector2i::snappedi(int32_t p_step) const {
+ return Vector2i(
+ Math::snapped(x, p_step),
+ Math::snapped(y, p_step));
+}
+
int64_t Vector2i::length_squared() const {
return x * (int64_t)x + y * (int64_t)y;
}
diff --git a/core/math/vector2i.h b/core/math/vector2i.h
index aa29263a65..fff9b0a658 100644
--- a/core/math/vector2i.h
+++ b/core/math/vector2i.h
@@ -37,7 +37,7 @@
class String;
struct Vector2;
-struct _NO_DISCARD_ Vector2i {
+struct [[nodiscard]] Vector2i {
static const int AXIS_COUNT = 2;
enum Axis {
@@ -81,10 +81,18 @@ struct _NO_DISCARD_ Vector2i {
return Vector2i(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y));
}
+ Vector2i mini(int32_t p_scalar) const {
+ return Vector2i(MIN(x, p_scalar), MIN(y, p_scalar));
+ }
+
Vector2i max(const Vector2i &p_vector2i) const {
return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y));
}
+ Vector2i maxi(int32_t p_scalar) const {
+ return Vector2i(MAX(x, p_scalar), MAX(y, p_scalar));
+ }
+
double distance_to(const Vector2i &p_to) const {
return (p_to - *this).length();
}
@@ -127,7 +135,9 @@ struct _NO_DISCARD_ Vector2i {
Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); }
Vector2i abs() const { return Vector2i(Math::abs(x), Math::abs(y)); }
Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const;
+ Vector2i clampi(int32_t p_min, int32_t p_max) const;
Vector2i snapped(const Vector2i &p_step) const;
+ Vector2i snappedi(int32_t p_step) const;
operator String() const;
operator Vector2() const;
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index fad5f2c0fb..1e90002665 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -52,6 +52,13 @@ Vector3 Vector3::clamp(const Vector3 &p_min, const Vector3 &p_max) const {
CLAMP(z, p_min.z, p_max.z));
}
+Vector3 Vector3::clampf(real_t p_min, real_t p_max) const {
+ return Vector3(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max),
+ CLAMP(z, p_min, p_max));
+}
+
void Vector3::snap(const Vector3 &p_step) {
x = Math::snapped(x, p_step.x);
y = Math::snapped(y, p_step.y);
@@ -64,6 +71,18 @@ Vector3 Vector3::snapped(const Vector3 &p_step) const {
return v;
}
+void Vector3::snapf(real_t p_step) {
+ x = Math::snapped(x, p_step);
+ y = Math::snapped(y, p_step);
+ z = Math::snapped(z, p_step);
+}
+
+Vector3 Vector3::snappedf(real_t p_step) const {
+ Vector3 v = *this;
+ v.snapf(p_step);
+ return v;
+}
+
Vector3 Vector3::limit_length(real_t p_len) const {
const real_t l = length();
Vector3 v = *this;
diff --git a/core/math/vector3.h b/core/math/vector3.h
index f5d16984d9..14bc44c4e7 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -39,7 +39,7 @@ struct Basis;
struct Vector2;
struct Vector3i;
-struct _NO_DISCARD_ Vector3 {
+struct [[nodiscard]] Vector3 {
static const int AXIS_COUNT = 3;
enum Axis {
@@ -80,10 +80,18 @@ struct _NO_DISCARD_ Vector3 {
return Vector3(MIN(x, p_vector3.x), MIN(y, p_vector3.y), MIN(z, p_vector3.z));
}
+ Vector3 minf(real_t p_scalar) const {
+ return Vector3(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar));
+ }
+
Vector3 max(const Vector3 &p_vector3) const {
return Vector3(MAX(x, p_vector3.x), MAX(y, p_vector3.y), MAX(z, p_vector3.z));
}
+ Vector3 maxf(real_t p_scalar) const {
+ return Vector3(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar));
+ }
+
_FORCE_INLINE_ real_t length() const;
_FORCE_INLINE_ real_t length_squared() const;
@@ -96,7 +104,9 @@ struct _NO_DISCARD_ Vector3 {
_FORCE_INLINE_ void zero();
void snap(const Vector3 &p_step);
+ void snapf(real_t p_step);
Vector3 snapped(const Vector3 &p_step) const;
+ Vector3 snappedf(real_t p_step) const;
void rotate(const Vector3 &p_axis, real_t p_angle);
Vector3 rotated(const Vector3 &p_axis, real_t p_angle) const;
@@ -127,6 +137,7 @@ struct _NO_DISCARD_ Vector3 {
_FORCE_INLINE_ Vector3 ceil() const;
_FORCE_INLINE_ Vector3 round() const;
Vector3 clamp(const Vector3 &p_min, const Vector3 &p_max) const;
+ Vector3 clampf(real_t p_min, real_t p_max) const;
_FORCE_INLINE_ real_t distance_to(const Vector3 &p_to) const;
_FORCE_INLINE_ real_t distance_squared_to(const Vector3 &p_to) const;
diff --git a/core/math/vector3i.cpp b/core/math/vector3i.cpp
index f41460e623..93f9d15ac1 100644
--- a/core/math/vector3i.cpp
+++ b/core/math/vector3i.cpp
@@ -48,6 +48,13 @@ Vector3i Vector3i::clamp(const Vector3i &p_min, const Vector3i &p_max) const {
CLAMP(z, p_min.z, p_max.z));
}
+Vector3i Vector3i::clampi(int32_t p_min, int32_t p_max) const {
+ return Vector3i(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max),
+ CLAMP(z, p_min, p_max));
+}
+
Vector3i Vector3i::snapped(const Vector3i &p_step) const {
return Vector3i(
Math::snapped(x, p_step.x),
@@ -55,6 +62,13 @@ Vector3i Vector3i::snapped(const Vector3i &p_step) const {
Math::snapped(z, p_step.z));
}
+Vector3i Vector3i::snappedi(int32_t p_step) const {
+ return Vector3i(
+ Math::snapped(x, p_step),
+ Math::snapped(y, p_step),
+ Math::snapped(z, p_step));
+}
+
Vector3i::operator String() const {
return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ")";
}
diff --git a/core/math/vector3i.h b/core/math/vector3i.h
index a9f298bff1..40d0700bf7 100644
--- a/core/math/vector3i.h
+++ b/core/math/vector3i.h
@@ -37,7 +37,7 @@
class String;
struct Vector3;
-struct _NO_DISCARD_ Vector3i {
+struct [[nodiscard]] Vector3i {
static const int AXIS_COUNT = 3;
enum Axis {
@@ -73,10 +73,18 @@ struct _NO_DISCARD_ Vector3i {
return Vector3i(MIN(x, p_vector3i.x), MIN(y, p_vector3i.y), MIN(z, p_vector3i.z));
}
+ Vector3i mini(int32_t p_scalar) const {
+ return Vector3i(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar));
+ }
+
Vector3i max(const Vector3i &p_vector3i) const {
return Vector3i(MAX(x, p_vector3i.x), MAX(y, p_vector3i.y), MAX(z, p_vector3i.z));
}
+ Vector3i maxi(int32_t p_scalar) const {
+ return Vector3i(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar));
+ }
+
_FORCE_INLINE_ int64_t length_squared() const;
_FORCE_INLINE_ double length() const;
@@ -85,7 +93,9 @@ struct _NO_DISCARD_ Vector3i {
_FORCE_INLINE_ Vector3i abs() const;
_FORCE_INLINE_ Vector3i sign() const;
Vector3i clamp(const Vector3i &p_min, const Vector3i &p_max) const;
+ Vector3i clampi(int32_t p_min, int32_t p_max) const;
Vector3i snapped(const Vector3i &p_step) const;
+ Vector3i snappedi(int32_t p_step) const;
_FORCE_INLINE_ double distance_to(const Vector3i &p_to) const;
_FORCE_INLINE_ int64_t distance_squared_to(const Vector3i &p_to) const;
diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp
index e6f6dee42c..b6b914f36d 100644
--- a/core/math/vector4.cpp
+++ b/core/math/vector4.cpp
@@ -30,6 +30,8 @@
#include "vector4.h"
+#include "core/math/math_funcs.h"
+#include "core/math/vector4i.h"
#include "core/string/ustring.h"
Vector4::Axis Vector4::min_axis_index() const {
@@ -171,12 +173,25 @@ void Vector4::snap(const Vector4 &p_step) {
w = Math::snapped(w, p_step.w);
}
+void Vector4::snapf(real_t p_step) {
+ x = Math::snapped(x, p_step);
+ y = Math::snapped(y, p_step);
+ z = Math::snapped(z, p_step);
+ w = Math::snapped(w, p_step);
+}
+
Vector4 Vector4::snapped(const Vector4 &p_step) const {
Vector4 v = *this;
v.snap(p_step);
return v;
}
+Vector4 Vector4::snappedf(real_t p_step) const {
+ Vector4 v = *this;
+ v.snapf(p_step);
+ return v;
+}
+
Vector4 Vector4::inverse() const {
return Vector4(1.0f / x, 1.0f / y, 1.0f / z, 1.0f / w);
}
@@ -189,8 +204,20 @@ Vector4 Vector4::clamp(const Vector4 &p_min, const Vector4 &p_max) const {
CLAMP(w, p_min.w, p_max.w));
}
+Vector4 Vector4::clampf(real_t p_min, real_t p_max) const {
+ return Vector4(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max),
+ CLAMP(z, p_min, p_max),
+ CLAMP(w, p_min, p_max));
+}
+
Vector4::operator String() const {
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")";
}
static_assert(sizeof(Vector4) == 4 * sizeof(real_t));
+
+Vector4::operator Vector4i() const {
+ return Vector4i(x, y, z, w);
+}
diff --git a/core/math/vector4.h b/core/math/vector4.h
index 4dba3126cb..8632f69f57 100644
--- a/core/math/vector4.h
+++ b/core/math/vector4.h
@@ -32,11 +32,13 @@
#define VECTOR4_H
#include "core/error/error_macros.h"
-#include "core/math/math_funcs.h"
+#include "core/math/math_defs.h"
+#include "core/typedefs.h"
class String;
+struct Vector4i;
-struct _NO_DISCARD_ Vector4 {
+struct [[nodiscard]] Vector4 {
static const int AXIS_COUNT = 4;
enum Axis {
@@ -72,10 +74,18 @@ struct _NO_DISCARD_ Vector4 {
return Vector4(MIN(x, p_vector4.x), MIN(y, p_vector4.y), MIN(z, p_vector4.z), MIN(w, p_vector4.w));
}
+ Vector4 minf(real_t p_scalar) const {
+ return Vector4(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar), MIN(w, p_scalar));
+ }
+
Vector4 max(const Vector4 &p_vector4) const {
return Vector4(MAX(x, p_vector4.x), MAX(y, p_vector4.y), MAX(z, p_vector4.z), MAX(w, p_vector4.w));
}
+ Vector4 maxf(real_t p_scalar) const {
+ return Vector4(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar), MAX(w, p_scalar));
+ }
+
_FORCE_INLINE_ real_t length_squared() const;
bool is_equal_approx(const Vector4 &p_vec4) const;
bool is_zero_approx() const;
@@ -101,8 +111,11 @@ struct _NO_DISCARD_ Vector4 {
Vector4 posmod(real_t p_mod) const;
Vector4 posmodv(const Vector4 &p_modv) const;
void snap(const Vector4 &p_step);
+ void snapf(real_t p_step);
Vector4 snapped(const Vector4 &p_step) const;
+ Vector4 snappedf(real_t p_step) const;
Vector4 clamp(const Vector4 &p_min, const Vector4 &p_max) const;
+ Vector4 clampf(real_t p_min, real_t p_max) const;
Vector4 inverse() const;
_FORCE_INLINE_ real_t dot(const Vector4 &p_vec4) const;
@@ -129,28 +142,14 @@ struct _NO_DISCARD_ Vector4 {
_FORCE_INLINE_ bool operator<=(const Vector4 &p_vec4) const;
operator String() const;
+ operator Vector4i() const;
_FORCE_INLINE_ Vector4() {}
-
- _FORCE_INLINE_ Vector4(real_t p_x, real_t p_y, real_t p_z, real_t p_w) :
- x(p_x),
- y(p_y),
- z(p_z),
- w(p_w) {
- }
-
- Vector4(const Vector4 &p_vec4) :
- x(p_vec4.x),
- y(p_vec4.y),
- z(p_vec4.z),
- w(p_vec4.w) {
- }
-
- void operator=(const Vector4 &p_vec4) {
- x = p_vec4.x;
- y = p_vec4.y;
- z = p_vec4.z;
- w = p_vec4.w;
+ _FORCE_INLINE_ Vector4(real_t p_x, real_t p_y, real_t p_z, real_t p_w) {
+ x = p_x;
+ y = p_y;
+ z = p_z;
+ w = p_w;
}
};
diff --git a/core/math/vector4i.cpp b/core/math/vector4i.cpp
index 8e36c6b534..afa77a4988 100644
--- a/core/math/vector4i.cpp
+++ b/core/math/vector4i.cpp
@@ -65,6 +65,14 @@ Vector4i Vector4i::clamp(const Vector4i &p_min, const Vector4i &p_max) const {
CLAMP(w, p_min.w, p_max.w));
}
+Vector4i Vector4i::clampi(int32_t p_min, int32_t p_max) const {
+ return Vector4i(
+ CLAMP(x, p_min, p_max),
+ CLAMP(y, p_min, p_max),
+ CLAMP(z, p_min, p_max),
+ CLAMP(w, p_min, p_max));
+}
+
Vector4i Vector4i::snapped(const Vector4i &p_step) const {
return Vector4i(
Math::snapped(x, p_step.x),
@@ -73,6 +81,14 @@ Vector4i Vector4i::snapped(const Vector4i &p_step) const {
Math::snapped(w, p_step.w));
}
+Vector4i Vector4i::snappedi(int32_t p_step) const {
+ return Vector4i(
+ Math::snapped(x, p_step),
+ Math::snapped(y, p_step),
+ Math::snapped(z, p_step),
+ Math::snapped(w, p_step));
+}
+
Vector4i::operator String() const {
return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ", " + itos(w) + ")";
}
diff --git a/core/math/vector4i.h b/core/math/vector4i.h
index 5a96d98d18..a9036d684a 100644
--- a/core/math/vector4i.h
+++ b/core/math/vector4i.h
@@ -37,7 +37,7 @@
class String;
struct Vector4;
-struct _NO_DISCARD_ Vector4i {
+struct [[nodiscard]] Vector4i {
static const int AXIS_COUNT = 4;
enum Axis {
@@ -75,10 +75,18 @@ struct _NO_DISCARD_ Vector4i {
return Vector4i(MIN(x, p_vector4i.x), MIN(y, p_vector4i.y), MIN(z, p_vector4i.z), MIN(w, p_vector4i.w));
}
+ Vector4i mini(int32_t p_scalar) const {
+ return Vector4i(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar), MIN(w, p_scalar));
+ }
+
Vector4i max(const Vector4i &p_vector4i) const {
return Vector4i(MAX(x, p_vector4i.x), MAX(y, p_vector4i.y), MAX(z, p_vector4i.z), MAX(w, p_vector4i.w));
}
+ Vector4i maxi(int32_t p_scalar) const {
+ return Vector4i(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar), MAX(w, p_scalar));
+ }
+
_FORCE_INLINE_ int64_t length_squared() const;
_FORCE_INLINE_ double length() const;
@@ -90,7 +98,9 @@ struct _NO_DISCARD_ Vector4i {
_FORCE_INLINE_ Vector4i abs() const;
_FORCE_INLINE_ Vector4i sign() const;
Vector4i clamp(const Vector4i &p_min, const Vector4i &p_max) const;
+ Vector4i clampi(int32_t p_min, int32_t p_max) const;
Vector4i snapped(const Vector4i &p_step) const;
+ Vector4i snappedi(int32_t p_step) const;
/* Operators */
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 80a2703c2f..ceeb04b8ea 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -31,7 +31,6 @@
#include "class_db.h"
#include "core/config/engine.h"
-#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/mutex.h"
@@ -77,6 +76,21 @@ class PlaceholderExtensionInstance {
StringName class_name;
HashMap<StringName, Variant> properties;
+ // Checks if a property is from a runtime class, and not a non-runtime base class.
+ bool is_runtime_property(const StringName &p_property_name) {
+ StringName current_class_name = class_name;
+
+ while (ClassDB::is_class_runtime(current_class_name)) {
+ if (ClassDB::has_property(current_class_name, p_property_name, true)) {
+ return true;
+ }
+
+ current_class_name = ClassDB::get_parent_class(current_class_name);
+ }
+
+ return false;
+ }
+
public:
PlaceholderExtensionInstance(const StringName &p_class_name) {
class_name = p_class_name;
@@ -84,27 +98,24 @@ public:
~PlaceholderExtensionInstance() {}
- void set(const StringName &p_name, const Variant &p_value) {
- bool is_default_valid = false;
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid);
-
- // If there's a default value, then we know it's a valid property.
- if (is_default_valid) {
+ void set(const StringName &p_name, const Variant &p_value, bool &r_valid) {
+ r_valid = is_runtime_property(p_name);
+ if (r_valid) {
properties[p_name] = p_value;
}
}
- Variant get(const StringName &p_name) {
+ Variant get(const StringName &p_name, bool &r_valid) {
const Variant *value = properties.getptr(p_name);
Variant ret;
if (value) {
ret = *value;
+ r_valid = true;
} else {
- bool is_default_valid = false;
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid);
- if (is_default_valid) {
- ret = default_value;
+ r_valid = is_runtime_property(p_name);
+ if (r_valid) {
+ ret = ClassDB::class_get_default_property_value(class_name, p_name);
}
}
@@ -116,10 +127,10 @@ public:
const StringName &name = *(StringName *)p_name;
const Variant &value = *(const Variant *)p_value;
- self->set(name, value);
+ bool valid = false;
+ self->set(name, value, valid);
- // We have to return true so Godot doesn't try to call the real setter function.
- return true;
+ return valid;
}
static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) {
@@ -127,10 +138,10 @@ public:
const StringName &name = *(StringName *)p_name;
Variant *value = (Variant *)r_ret;
- *value = self->get(name);
+ bool valid = false;
+ *value = self->get(name, valid);
- // We have to return true so Godot doesn't try to call the real getter function.
- return true;
+ return valid;
}
static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) {
@@ -138,7 +149,7 @@ public:
return nullptr;
}
- static void placeholder_instance_free_property_list(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list) {
+ static void placeholder_instance_free_property_list(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list, uint32_t p_count) {
}
static GDExtensionBool placeholder_instance_property_can_revert(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name) {
@@ -173,17 +184,29 @@ public:
static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
- // Find the closest native parent.
+ // Find the closest native parent, that isn't a runtime class.
ClassDB::ClassInfo *native_parent = ti->inherits_ptr;
- while (native_parent->gdextension) {
+ while (native_parent->gdextension || native_parent->is_runtime) {
native_parent = native_parent->inherits_ptr;
}
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
// Construct a placeholder.
Object *obj = native_parent->creation_func();
+
+ // ClassDB::set_object_extension_instance() won't be called for placeholders.
+ // We need need to make sure that all the things it would have done (even if
+ // done in a different way to support placeholders) will also be done here.
+
obj->_extension = ClassDB::get_placeholder_extension(ti->name);
obj->_extension_instance = memnew(PlaceholderExtensionInstance(ti->name));
+
+#ifdef TOOLS_ENABLED
+ if (obj->_extension->track_instance) {
+ obj->_extension->track_instance(obj->_extension->tracking_userdata, obj);
+ }
+#endif
+
return obj;
}
@@ -413,8 +436,8 @@ uint32_t ClassDB::get_api_hash(APIType p_api) {
for (const StringName &F : snames) {
MethodInfo &mi = t->signal_map[F];
hash = hash_murmur3_one_64(F.hash(), hash);
- for (int i = 0; i < mi.arguments.size(); i++) {
- hash = hash_murmur3_one_64(mi.arguments[i].type, hash);
+ for (const PropertyInfo &pi : mi.arguments) {
+ hash = hash_murmur3_one_64(pi.type, hash);
}
}
}
@@ -494,7 +517,7 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated.");
}
#ifdef TOOLS_ENABLED
- if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) {
+ 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.");
return nullptr;
}
@@ -506,19 +529,12 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
extension = get_placeholder_extension(ti->name);
}
#endif
- Object *obj = (Object *)extension->create_instance(extension->class_userdata);
-
-#ifdef TOOLS_ENABLED
- if (extension->track_instance) {
- extension->track_instance(extension->tracking_userdata, obj);
- }
-#endif
- return obj;
+ return (Object *)extension->create_instance(extension->class_userdata);
} else {
#ifdef TOOLS_ENABLED
if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) {
if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) {
- ERR_PRINT_ONCE(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name));
+ ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name));
} else {
ObjectGDExtension *extension = get_placeholder_extension(ti->name);
return (Object *)extension->create_instance(extension->class_userdata);
@@ -595,12 +611,13 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class)
placeholder_extension->set = &PlaceholderExtensionInstance::placeholder_instance_set;
placeholder_extension->get = &PlaceholderExtensionInstance::placeholder_instance_get;
placeholder_extension->get_property_list = &PlaceholderExtensionInstance::placeholder_instance_get_property_list;
- placeholder_extension->free_property_list = &PlaceholderExtensionInstance::placeholder_instance_free_property_list;
+ placeholder_extension->free_property_list2 = &PlaceholderExtensionInstance::placeholder_instance_free_property_list;
placeholder_extension->property_can_revert = &PlaceholderExtensionInstance::placeholder_instance_property_can_revert;
placeholder_extension->property_get_revert = &PlaceholderExtensionInstance::placeholder_instance_property_get_revert;
placeholder_extension->validate_property = &PlaceholderExtensionInstance::placeholder_instance_validate_property;
#ifndef DISABLE_DEPRECATED
placeholder_extension->notification = nullptr;
+ placeholder_extension->free_property_list = nullptr;
#endif // DISABLE_DEPRECATED
placeholder_extension->notification2 = &PlaceholderExtensionInstance::placeholder_instance_notification;
placeholder_extension->to_string = &PlaceholderExtensionInstance::placeholder_instance_to_string;
@@ -638,6 +655,12 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
p_object->_extension = ti->gdextension;
p_object->_extension_instance = p_instance;
+
+#ifdef TOOLS_ENABLED
+ if (p_object->_extension->track_instance) {
+ p_object->_extension->track_instance(p_object->_extension->tracking_userdata, p_object);
+ }
+#endif
}
bool ClassDB::can_instantiate(const StringName &p_class) {
@@ -653,13 +676,28 @@ bool ClassDB::can_instantiate(const StringName &p_class) {
return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
}
#ifdef TOOLS_ENABLED
- if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) {
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
return false;
}
#endif
return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance));
}
+bool ClassDB::is_abstract(const StringName &p_class) {
+ 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) + "'.");
+ }
+ 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();
+ }
+ return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr);
+}
+
bool ClassDB::is_virtual(const StringName &p_class) {
OBJTYPE_RLOCK;
@@ -673,7 +711,7 @@ bool ClassDB::is_virtual(const StringName &p_class) {
return scr.is_valid() && scr->is_valid() && scr->is_abstract();
}
#ifdef TOOLS_ENABLED
- if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) {
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
return false;
}
#endif
@@ -1542,7 +1580,7 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia
}
// The "free()" method is special, so we assume it exists and return a Callable.
- if (p_property == CoreStringNames::get_singleton()->_free) {
+ if (p_property == CoreStringName(free_)) {
r_value = Callable(p_object, p_property);
return true;
}
@@ -1844,8 +1882,9 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (p_arg_names.size() != mi.arguments.size()) {
WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name);
} else {
- for (int i = 0; i < p_arg_names.size(); i++) {
- mi.arguments[i].name = p_arg_names[i];
+ List<PropertyInfo>::Iterator itr = mi.arguments.begin();
+ for (int i = 0; i < p_arg_names.size(); ++itr, ++i) {
+ itr->name = p_arg_names[i];
}
}
}
@@ -1940,6 +1979,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) {
return ti->reloadable;
}
+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) + "'.");
+ return ti->is_runtime;
+}
+
void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) {
if (resource_base_extensions.has(p_extension)) {
return;
@@ -2051,6 +2098,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
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");
+#endif
+
ClassInfo c;
c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION;
c.gdextension = p_extension;
diff --git a/core/object/class_db.h b/core/object/class_db.h
index adb525cbe8..228b82b588 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -195,7 +195,7 @@ public:
template <typename T>
static void register_class(bool p_virtual = false) {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -210,7 +210,7 @@ public:
template <typename T>
static void register_abstract_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -223,7 +223,7 @@ public:
template <typename T>
static void register_internal_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -238,7 +238,7 @@ public:
template <typename T>
static void register_runtime_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -263,7 +263,7 @@ public:
template <typename T>
static void register_custom_instance_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -287,6 +287,7 @@ public:
static bool class_exists(const StringName &p_class);
static bool is_parent_class(const StringName &p_class, const StringName &p_inherits);
static bool can_instantiate(const StringName &p_class);
+ static bool is_abstract(const StringName &p_class);
static bool is_virtual(const StringName &p_class);
static Object *instantiate(const StringName &p_class);
static Object *instantiate_no_placeholders(const StringName &p_class);
@@ -460,6 +461,7 @@ public:
static bool is_class_exposed(const StringName &p_class);
static bool is_class_reloadable(const StringName &p_class);
+ static bool is_class_runtime(const StringName &p_class);
static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class);
static void get_resource_base_extensions(List<String> *p_extensions);
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp
index 90536e58ff..4b0b1ce63d 100644
--- a/core/object/message_queue.cpp
+++ b/core/object/message_queue.cpp
@@ -31,7 +31,6 @@
#include "message_queue.h"
#include "core/config/project_settings.h"
-#include "core/core_string_names.h"
#include "core/object/class_db.h"
#include "core/object/script_language.h"
@@ -197,7 +196,7 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) {
Message *msg = memnew_placement(buffer_end, Message);
msg->type = TYPE_NOTIFICATION;
- msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it
+ msg->callable = Callable(p_id, CoreStringName(notification)); //name is meaningless but callable needs it
//msg->target;
msg->notification = p_notification;
@@ -224,64 +223,7 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args
}
}
-Error CallQueue::_transfer_messages_to_main_queue() {
- if (pages.size() == 0) {
- return OK;
- }
-
- CallQueue *mq = MessageQueue::main_singleton;
- DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters.
-
- mq->mutex.lock();
-
- // Here we're transferring the data from this queue to the main one.
- // However, it's very unlikely big amounts of messages will be queued here,
- // so PagedArray/Pool would be overkill. Also, in most cases the data will fit
- // an already existing page of the main queue.
-
- // Let's see if our first (likely only) page fits the current target queue page.
- uint32_t src_page = 0;
- {
- if (mq->pages_used) {
- uint32_t dst_page = mq->pages_used - 1;
- uint32_t dst_offset = mq->page_bytes[dst_page];
- if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) {
- memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]);
- mq->page_bytes[dst_page] += page_bytes[0];
- src_page++;
- }
- }
- }
-
- // Any other possibly existing source page needs to be added.
-
- if (mq->pages_used + (pages_used - src_page) > mq->max_pages) {
- fprintf(stderr, "Failed appending thread queue. Message queue out of memory. %s\n", mq->error_text.utf8().get_data());
- mq->statistics();
- mq->mutex.unlock();
- return ERR_OUT_OF_MEMORY;
- }
-
- for (; src_page < pages_used; src_page++) {
- mq->_add_page();
- memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]);
- mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page];
- }
-
- mq->mutex.unlock();
-
- page_bytes[0] = 0;
- pages_used = 1;
-
- return OK;
-}
-
Error CallQueue::flush() {
- // Thread overrides are not meant to be flushed, but appended to the main one.
- if (unlikely(this == MessageQueue::thread_singleton)) {
- return _transfer_messages_to_main_queue();
- }
-
LOCK_MUTEX;
if (pages.size() == 0) {
@@ -539,10 +481,7 @@ CallQueue::~CallQueue() {
if (!allocator_is_custom) {
memdelete(allocator);
}
- // This is done here to avoid a circular dependency between the safety checks and the thread singleton pointer.
- if (this == MessageQueue::thread_singleton) {
- MessageQueue::thread_singleton = nullptr;
- }
+ DEV_ASSERT(!is_current_thread_override);
}
//////////////////////
@@ -551,7 +490,6 @@ CallQueue *MessageQueue::main_singleton = nullptr;
thread_local CallQueue *MessageQueue::thread_singleton = nullptr;
void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) {
- DEV_ASSERT(p_thread_singleton); // To unset the thread singleton, don't call this with nullptr, but just memfree() it.
#ifdef DEV_ENABLED
if (thread_singleton) {
thread_singleton->is_current_thread_override = false;
diff --git a/core/object/message_queue.h b/core/object/message_queue.h
index c2f4ad1643..673eb3845b 100644
--- a/core/object/message_queue.h
+++ b/core/object/message_queue.h
@@ -98,8 +98,6 @@ private:
}
}
- Error _transfer_messages_to_main_queue();
-
void _add_page();
void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
@@ -166,6 +164,7 @@ class MessageQueue : public CallQueue {
public:
_FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; }
+ _FORCE_INLINE_ static CallQueue *get_main_singleton() { return main_singleton; }
static void set_thread_singleton_override(CallQueue *p_thread_singleton);
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index e97f4abc6a..2f9a2d1679 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -152,7 +152,7 @@ public:
if (p_arg < 0) {
return _gen_return_type_info();
} else if (p_arg < method_info.arguments.size()) {
- return method_info.arguments[p_arg];
+ return method_info.arguments.get(p_arg);
} else {
return PropertyInfo(Variant::NIL, "arg_" + itos(p_arg), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT);
}
@@ -193,10 +193,11 @@ public:
Vector<StringName> names;
names.resize(method_info.arguments.size());
#endif
- for (int i = 0; i < method_info.arguments.size(); i++) {
- at[i + 1] = method_info.arguments[i].type;
+ int i = 0;
+ for (List<PropertyInfo>::ConstIterator itr = method_info.arguments.begin(); itr != method_info.arguments.end(); ++itr, ++i) {
+ at[i + 1] = itr->type;
#ifdef DEBUG_METHODS_ENABLED
- names.write[i] = method_info.arguments[i].name;
+ names.write[i] = itr->name;
#endif
}
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 06f6e8e9e6..ac23bf831f 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -31,7 +31,6 @@
#include "object.h"
#include "object.compat.inc"
-#include "core/core_string_names.h"
#include "core/extension/gdextension_manager.h"
#include "core/io/resource.h"
#include "core/object/class_db.h"
@@ -39,7 +38,7 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
@@ -237,20 +236,12 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid
}
if (_extension && _extension->set) {
-// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wignored-qualifiers"
-#endif
- if (_extension->set(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (const GDExtensionVariantPtr)&p_value)) {
+ if (_extension->set(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionConstVariantPtr)&p_value)) {
if (r_valid) {
*r_valid = true;
}
return;
}
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
}
// Try built-in setter.
@@ -260,7 +251,7 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid
}
}
- if (p_name == CoreStringNames::get_singleton()->_script) {
+ if (p_name == CoreStringName(script)) {
set_script(p_value);
if (r_valid) {
*r_valid = true;
@@ -324,21 +315,12 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
}
}
if (_extension && _extension->get) {
-// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wignored-qualifiers"
-#endif
-
- if (_extension->get(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) {
+ if (_extension->get(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) {
if (r_valid) {
*r_valid = true;
}
return ret;
}
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
}
// Try built-in getter.
@@ -351,7 +333,7 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
}
}
- if (p_name == CoreStringNames::get_singleton()->_script) {
+ if (p_name == CoreStringName(script)) {
ret = get_script();
if (r_valid) {
*r_valid = true;
@@ -503,9 +485,14 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
for (uint32_t i = 0; i < pcount; i++) {
p_list->push_back(PropertyInfo(pinfo[i]));
}
- if (current_extension->free_property_list) {
+ if (current_extension->free_property_list2) {
+ current_extension->free_property_list2(_extension_instance, pinfo, pcount);
+ }
+#ifndef DISABLE_DEPRECATED
+ else if (current_extension->free_property_list) {
current_extension->free_property_list(_extension_instance, pinfo);
}
+#endif // DISABLE_DEPRECATED
#ifdef TOOLS_ENABLED
}
#endif
@@ -571,19 +558,11 @@ bool Object::property_can_revert(const StringName &p_name) const {
}
}
-// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wignored-qualifiers"
-#endif
if (_extension && _extension->property_can_revert) {
- if (_extension->property_can_revert(_extension_instance, (const GDExtensionStringNamePtr)&p_name)) {
+ if (_extension->property_can_revert(_extension_instance, (GDExtensionConstStringNamePtr)&p_name)) {
return true;
}
}
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
return _property_can_revertv(p_name);
}
@@ -597,19 +576,11 @@ Variant Object::property_get_revert(const StringName &p_name) const {
}
}
-// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wignored-qualifiers"
-#endif
if (_extension && _extension->property_get_revert) {
- if (_extension->property_get_revert(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) {
+ if (_extension->property_get_revert(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) {
return ret;
}
}
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
if (_property_get_revertv(p_name, ret)) {
return ret;
@@ -667,7 +638,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call
}
bool Object::has_method(const StringName &p_method) const {
- if (p_method == CoreStringNames::get_singleton()->_free) {
+ if (p_method == CoreStringName(free_)) {
return true;
}
@@ -693,7 +664,7 @@ int Object::_get_method_argument_count_bind(const StringName &p_method) const {
}
int Object::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
- if (p_method == CoreStringNames::get_singleton()->_free) {
+ if (p_method == CoreStringName(free_)) {
if (r_is_valid) {
*r_is_valid = true;
}
@@ -782,7 +753,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
r_error.error = Callable::CallError::CALL_OK;
- if (p_method == CoreStringNames::get_singleton()->_free) {
+ if (p_method == CoreStringName(free_)) {
//free must be here, before anything, always ready
#ifdef DEBUG_ENABLED
if (p_argcount != 0) {
@@ -792,7 +763,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
}
if (is_ref_counted()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference.");
+ ERR_FAIL_V_MSG(Variant(), "Can't free a RefCounted object.");
}
if (_lock_index.get() > 1) {
@@ -845,7 +816,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
Variant Object::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
r_error.error = Callable::CallError::CALL_OK;
- if (p_method == CoreStringNames::get_singleton()->_free) {
+ if (p_method == CoreStringName(free_)) {
// Free is not const, so fail.
r_error.error = Callable::CallError::CALL_ERROR_METHOD_NOT_CONST;
return Variant();
@@ -919,6 +890,7 @@ void Object::notification(int p_notification, bool p_reversed) {
}
String Object::to_string() {
+ // Keep this method in sync with `Node::to_string`.
if (script_instance) {
bool valid;
String ret = script_instance->to_string(&valid);
@@ -974,7 +946,7 @@ void Object::set_script(const Variant &p_script) {
}
notify_property_list_changed(); //scripts may add variables, so refresh is desired
- emit_signal(CoreStringNames::get_singleton()->script_changed);
+ emit_signal(CoreStringName(script_changed));
}
void Object::set_script_instance(ScriptInstance *p_instance) {
@@ -1100,6 +1072,20 @@ bool Object::_has_user_signal(const StringName &p_name) const {
return signal_map[p_name].user.name.length() > 0;
}
+void Object::_remove_user_signal(const StringName &p_name) {
+ SignalData *s = signal_map.getptr(p_name);
+ ERR_FAIL_NULL_MSG(s, "Provided signal does not exist.");
+ ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal).");
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ Object *target = slot_kv.key.get_object();
+ if (likely(target)) {
+ target->connections.erase(slot_kv.value.cE);
+ }
+ }
+
+ signal_map.erase(p_name);
+}
+
Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (unlikely(p_argcount < 1)) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -1248,6 +1234,10 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) {
}
add_user_signal(mi);
+
+ if (signal_map.has(p_name)) {
+ signal_map.getptr(p_name)->removable = true;
+ }
}
TypedArray<Dictionary> Object::_get_signal_list() const {
@@ -1279,9 +1269,8 @@ TypedArray<Dictionary> Object::_get_signal_connection_list(const StringName &p_s
TypedArray<Dictionary> Object::_get_incoming_connections() const {
TypedArray<Dictionary> ret;
- int connections_amount = connections.size();
- for (int idx_conn = 0; idx_conn < connections_amount; idx_conn++) {
- ret.push_back(connections[idx_conn]);
+ for (const Object::Connection &connection : connections) {
+ ret.push_back(connection);
}
return ret;
@@ -1437,7 +1426,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.");
+ 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`.
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal);
@@ -1460,7 +1449,7 @@ 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.");
+ 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`.
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@@ -1632,7 +1621,7 @@ void Object::clear_internal_resource_paths() {
}
void Object::notify_property_list_changed() {
- emit_signal(CoreStringNames::get_singleton()->property_list_changed);
+ emit_signal(CoreStringName(property_list_changed));
}
void Object::_bind_methods() {
@@ -1661,6 +1650,7 @@ void Object::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal);
+ ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal);
{
MethodInfo mi;
@@ -2072,9 +2062,13 @@ Object::~Object() {
_extension_instance = nullptr;
}
#ifdef TOOLS_ENABLED
- else if (_instance_bindings != nullptr && Engine::get_singleton()->is_extension_reloading_enabled()) {
- for (uint32_t i = 0; i < _instance_binding_count; i++) {
- GDExtensionManager::get_singleton()->untrack_instance_binding(_instance_bindings[i].token, this);
+ else if (_instance_bindings != nullptr) {
+ Engine *engine = Engine::get_singleton();
+ GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton();
+ if (engine && gdextension_manager && engine->is_extension_reloading_enabled()) {
+ for (uint32_t i = 0; i < _instance_binding_count; i++) {
+ gdextension_manager->untrack_instance_binding(_instance_bindings[i].token, this);
+ }
}
}
#endif
@@ -2284,9 +2278,9 @@ void ObjectDB::setup() {
}
void ObjectDB::cleanup() {
- if (slot_count > 0) {
- spin_lock.lock();
+ spin_lock.lock();
+ if (slot_count > 0) {
WARN_PRINT("ObjectDB instances leaked at exit (run with --verbose for details).");
if (OS::get_singleton()->is_stdout_verbose()) {
// Ensure calling the native classes because if a leaked instance has a script
@@ -2317,10 +2311,11 @@ void ObjectDB::cleanup() {
}
print_line("Hint: Leaked instances typically happen when nodes are removed from the scene tree (with `remove_child()`) but not freed (with `free()` or `queue_free()`).");
}
- spin_lock.unlock();
}
if (object_slots) {
memfree(object_slots);
}
+
+ spin_lock.unlock();
}
diff --git a/core/object/object.h b/core/object/object.h
index d9551ecd01..adb50268d2 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -324,12 +324,13 @@ struct ObjectGDExtension {
GDExtensionClassSet set;
GDExtensionClassGet get;
GDExtensionClassGetPropertyList get_property_list;
- GDExtensionClassFreePropertyList free_property_list;
+ GDExtensionClassFreePropertyList2 free_property_list2;
GDExtensionClassPropertyCanRevert property_can_revert;
GDExtensionClassPropertyGetRevert property_get_revert;
GDExtensionClassValidateProperty validate_property;
#ifndef DISABLE_DEPRECATED
GDExtensionClassNotification notification;
+ GDExtensionClassFreePropertyList free_property_list;
#endif // DISABLE_DEPRECATED
GDExtensionClassNotification2 notification2;
GDExtensionClassToString to_string;
@@ -619,6 +620,7 @@ private:
MethodInfo user;
HashMap<Callable, Slot, HashableHasher<Callable>> slot_map;
+ bool removable = false;
};
HashMap<StringName, SignalData> signal_map;
@@ -646,6 +648,7 @@ private:
void _add_user_signal(const String &p_name, const Array &p_args = Array());
bool _has_user_signal(const StringName &p_name) const;
+ void _remove_user_signal(const StringName &p_name);
Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
TypedArray<Dictionary> _get_signal_list() const;
TypedArray<Dictionary> _get_signal_connection_list(const StringName &p_signal) const;
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 660e13e819..cdc56e5ec5 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -31,7 +31,6 @@
#include "script_language.h"
#include "core/config/project_settings.h"
-#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/io/resource_loader.h"
@@ -51,7 +50,7 @@ void Script::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
if (EngineDebugger::is_active()) {
- EngineDebugger::get_script_debugger()->set_break_language(get_language());
+ callable_mp(this, &Script::_set_debugger_break_language).call_deferred();
}
} break;
}
@@ -103,6 +102,12 @@ Dictionary Script::_get_script_constant_map() {
return ret;
}
+void Script::_set_debugger_break_language() {
+ if (EngineDebugger::is_active()) {
+ EngineDebugger::get_script_debugger()->set_break_language(get_language());
+ }
+}
+
int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
MethodInfo mi = get_method_info(p_method);
@@ -486,10 +491,6 @@ void ScriptServer::save_global_classes() {
ProjectSettings::get_singleton()->store_global_class_list(gcarr);
}
-String ScriptServer::get_global_class_cache_file_path() {
- return ProjectSettings::get_singleton()->get_global_class_list_path();
-}
-
////////////////////
ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr;
@@ -531,6 +532,7 @@ void ScriptLanguage::get_core_type_words(List<String> *p_core_type_words) const
p_core_type_words->push_back("PackedVector2Array");
p_core_type_words->push_back("PackedVector3Array");
p_core_type_words->push_back("PackedColorArray");
+ p_core_type_words->push_back("PackedVector4Array");
}
void ScriptLanguage::frame() {
@@ -691,7 +693,13 @@ bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const {
}
if (script.is_valid()) {
- return script->has_method(p_method);
+ Ref<Script> scr = script;
+ while (scr.is_valid()) {
+ if (scr->has_method(p_method)) {
+ return true;
+ }
+ scr = scr->get_base_script();
+ }
}
return false;
}
diff --git a/core/object/script_language.h b/core/object/script_language.h
index c6c6f3de9f..59a43a7b29 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -97,7 +97,6 @@ public:
static void get_global_class_list(List<StringName> *r_global_classes);
static void get_inheriters_list(const StringName &p_base_type, List<StringName> *r_classes);
static void save_global_classes();
- static String get_global_class_cache_file_path();
static void init_languages();
static void finish_languages();
@@ -124,6 +123,8 @@ protected:
TypedArray<Dictionary> _get_script_signal_list();
Dictionary _get_script_constant_map();
+ void _set_debugger_break_language();
+
public:
virtual void reload_from_file() override;
diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp
index a18ef8d4d7..7b643e4637 100644
--- a/core/object/script_language_extension.cpp
+++ b/core/object/script_language_extension.cpp
@@ -132,6 +132,7 @@ void ScriptLanguageExtension::_bind_methods() {
GDVIRTUAL_BIND(_debug_get_stack_level_line, "level");
GDVIRTUAL_BIND(_debug_get_stack_level_function, "level");
+ GDVIRTUAL_BIND(_debug_get_stack_level_source, "level");
GDVIRTUAL_BIND(_debug_get_stack_level_locals, "level", "max_subitems", "max_depth");
GDVIRTUAL_BIND(_debug_get_stack_level_members, "level", "max_subitems", "max_depth");
GDVIRTUAL_BIND(_debug_get_stack_level_instance, "level");
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index cc6b729ae8..c9344f5799 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -389,7 +389,16 @@ public:
EXBIND0RC(bool, can_make_function)
EXBIND3R(Error, open_in_external_editor, const Ref<Script> &, int, int)
EXBIND0R(bool, overrides_external_editor)
- EXBIND0RC(ScriptNameCasing, preferred_file_name_casing)
+
+ GDVIRTUAL0RC(ScriptNameCasing, _preferred_file_name_casing);
+
+ virtual ScriptNameCasing preferred_file_name_casing() const override {
+ ScriptNameCasing ret;
+ if (GDVIRTUAL_CALL(_preferred_file_name_casing, ret)) {
+ return ret;
+ }
+ return ScriptNameCasing::SCRIPT_NAME_CASING_SNAKE_CASE;
+ }
GDVIRTUAL3RC(Dictionary, _complete_code, const String &, const String &, Object *)
@@ -637,7 +646,7 @@ public:
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override {
int ret = 0;
- GDVIRTUAL_REQUIRED_CALL(_profiling_get_accumulated_data, p_info_arr, p_info_max, ret);
+ GDVIRTUAL_REQUIRED_CALL(_profiling_get_frame_data, p_info_arr, p_info_max, ret);
return ret;
}
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 6a1385e268..4d67cd930e 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -144,7 +144,7 @@ void UndoRedo::create_action(const String &p_name, MergeMode p_mode, bool p_back
}
void UndoRedo::add_do_method(const Callable &p_callable) {
- ERR_FAIL_COND(p_callable.is_null());
+ ERR_FAIL_COND(!p_callable.is_valid());
ERR_FAIL_COND(action_level <= 0);
ERR_FAIL_COND((current_action + 1) >= actions.size());
@@ -169,7 +169,7 @@ void UndoRedo::add_do_method(const Callable &p_callable) {
}
void UndoRedo::add_undo_method(const Callable &p_callable) {
- ERR_FAIL_COND(p_callable.is_null());
+ ERR_FAIL_COND(!p_callable.is_valid());
ERR_FAIL_COND(action_level <= 0);
ERR_FAIL_COND((current_action + 1) >= actions.size());
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index ef3d315e4b..56b9fa8475 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -33,7 +33,8 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/os/thread_safe.h"
-#include "core/templates/command_queue_mt.h"
+
+WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1;
void WorkerThreadPool::Task::free_template_userdata() {
ERR_FAIL_NULL(template_userdata);
@@ -44,34 +45,48 @@ void WorkerThreadPool::Task::free_template_userdata() {
WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
-thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr;
+#ifdef THREADS_ENABLED
+thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {};
+#endif
void WorkerThreadPool::_process_task(Task *p_task) {
#ifdef THREADS_ENABLED
int pool_thread_index = thread_ids[Thread::get_caller_id()];
ThreadData &curr_thread = threads[pool_thread_index];
Task *prev_task = nullptr; // In case this is recursively called.
+
bool safe_for_nodes_backup = is_current_thread_safe_for_nodes();
+ CallQueue *call_queue_backup = MessageQueue::get_singleton() != MessageQueue::get_main_singleton() ? MessageQueue::get_singleton() : nullptr;
{
- // Tasks must start with this unset. They are free to set-and-forget otherwise.
+ // Tasks must start with these at default values. They are free to set-and-forget otherwise.
set_current_thread_safe_for_nodes(false);
+ MessageQueue::set_thread_singleton_override(nullptr);
// Since the WorkerThreadPool is started before the script server,
// its pre-created threads can't have ScriptServer::thread_enter() called on them early.
// Therefore, we do it late at the first opportunity, so in case the task
// about to be run uses scripting, guarantees are held.
+ task_mutex.lock();
if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) {
+ task_mutex.unlock();
ScriptServer::thread_enter();
+ task_mutex.lock();
curr_thread.ready_for_scripting = true;
}
- task_mutex.lock();
p_task->pool_thread_index = pool_thread_index;
prev_task = curr_thread.current_task;
curr_thread.current_task = p_task;
+ if (p_task->pending_notify_yield_over) {
+ curr_thread.yield_is_over = true;
+ }
task_mutex.unlock();
}
#endif
+#ifdef THREADS_ENABLED
+ bool low_priority = p_task->low_priority;
+#endif
+
if (p_task->group) {
// Handling a group
bool do_post = false;
@@ -148,7 +163,7 @@ void WorkerThreadPool::_process_task(Task *p_task) {
#ifdef THREADS_ENABLED
{
curr_thread.current_task = prev_task;
- if (p_task->low_priority) {
+ if (low_priority) {
low_priority_threads_used--;
if (_try_promote_low_priority_task()) {
@@ -162,6 +177,7 @@ void WorkerThreadPool::_process_task(Task *p_task) {
}
set_current_thread_safe_for_nodes(safe_for_nodes_backup);
+ MessageQueue::set_thread_singleton_override(call_queue_backup);
#endif
}
@@ -386,86 +402,152 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
task->waiting_user++;
}
+ if (caller_pool_thread) {
+ task_mutex.unlock();
+ _wait_collaboratively(caller_pool_thread, task);
+ task_mutex.lock();
+ task->waiting_pool--;
+ if (task->waiting_pool == 0 && task->waiting_user == 0) {
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
+ }
+ } else {
+ task_mutex.unlock();
+ task->done_semaphore.wait();
+ task_mutex.lock();
+ task->waiting_user--;
+ if (task->waiting_pool == 0 && task->waiting_user == 0) {
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
+ }
+ }
+
task_mutex.unlock();
+ return OK;
+}
- if (caller_pool_thread) {
- while (true) {
- Task *task_to_process = nullptr;
- {
- MutexLock lock(task_mutex);
- bool was_signaled = caller_pool_thread->signaled;
- caller_pool_thread->signaled = false;
-
- if (task->completed) {
- // This thread was awaken also for some reason, but it's about to exit.
- // Let's find out what may be pending and forward the requests.
- if (!exit_threads && was_signaled) {
- uint32_t to_process = task_queue.first() ? 1 : 0;
- uint32_t to_promote = caller_pool_thread->current_task->low_priority && low_priority_task_queue.first() ? 1 : 0;
- if (to_process || to_promote) {
- // This thread must be left alone since it won't loop again.
- caller_pool_thread->signaled = true;
- _notify_threads(caller_pool_thread, to_process, to_promote);
- }
- }
+void WorkerThreadPool::_lock_unlockable_mutexes() {
+#ifdef THREADS_ENABLED
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlockable_mutexes[i]) {
+ if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
+ ((Mutex *)unlockable_mutexes[i])->lock();
+ } else {
+ ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->lock();
+ }
+ }
+ }
+#endif
+}
- task->waiting_pool--;
- if (task->waiting_pool == 0 && task->waiting_user == 0) {
- tasks.erase(p_task_id);
- task_allocator.free(task);
- }
+void WorkerThreadPool::_unlock_unlockable_mutexes() {
+#ifdef THREADS_ENABLED
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlockable_mutexes[i]) {
+ if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
+ ((Mutex *)unlockable_mutexes[i])->unlock();
+ } else {
+ ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->unlock();
+ }
+ }
+ }
+#endif
+}
- break;
- }
+void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) {
+ // Keep processing tasks until the condition to stop waiting is met.
- if (!exit_threads) {
- // This is a thread from the pool. It shouldn't just idle.
- // Let's try to process other tasks while we wait.
+#define IS_WAIT_OVER (unlikely(p_task == ThreadData::YIELDING) ? p_caller_pool_thread->yield_is_over : p_task->completed)
- if (caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) {
- if (_try_promote_low_priority_task()) {
- _notify_threads(caller_pool_thread, 1, 0);
- }
+ while (true) {
+ Task *task_to_process = nullptr;
+ bool relock_unlockables = false;
+ {
+ MutexLock lock(task_mutex);
+ bool was_signaled = p_caller_pool_thread->signaled;
+ p_caller_pool_thread->signaled = false;
+
+ if (IS_WAIT_OVER) {
+ p_caller_pool_thread->yield_is_over = false;
+ if (!exit_threads && was_signaled) {
+ // This thread was awaken for some additional reason, but it's about to exit.
+ // Let's find out what may be pending and forward the requests.
+ uint32_t to_process = task_queue.first() ? 1 : 0;
+ uint32_t to_promote = p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first() ? 1 : 0;
+ if (to_process || to_promote) {
+ // This thread must be left alone since it won't loop again.
+ p_caller_pool_thread->signaled = true;
+ _notify_threads(p_caller_pool_thread, to_process, to_promote);
}
+ }
+
+ break;
+ }
- if (singleton->task_queue.first()) {
- task_to_process = task_queue.first()->self();
- task_queue.remove(task_queue.first());
+ if (!exit_threads) {
+ if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) {
+ if (_try_promote_low_priority_task()) {
+ _notify_threads(p_caller_pool_thread, 1, 0);
}
+ }
- if (!task_to_process) {
- caller_pool_thread->awaited_task = task;
+ if (singleton->task_queue.first()) {
+ task_to_process = task_queue.first()->self();
+ task_queue.remove(task_queue.first());
+ }
- if (flushing_cmd_queue) {
- flushing_cmd_queue->unlock();
- }
- caller_pool_thread->cond_var.wait(lock);
- if (flushing_cmd_queue) {
- flushing_cmd_queue->lock();
- }
+ if (!task_to_process) {
+ p_caller_pool_thread->awaited_task = p_task;
- DEV_ASSERT(exit_threads || caller_pool_thread->signaled || task->completed);
- caller_pool_thread->awaited_task = nullptr;
- }
+ _unlock_unlockable_mutexes();
+ relock_unlockables = true;
+ p_caller_pool_thread->cond_var.wait(lock);
+
+ DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER);
+ p_caller_pool_thread->awaited_task = nullptr;
}
}
+ }
- if (task_to_process) {
- _process_task(task_to_process);
- }
+ if (relock_unlockables) {
+ _lock_unlockable_mutexes();
}
- } else {
- task->done_semaphore.wait();
- task_mutex.lock();
- task->waiting_user--;
- if (task->waiting_pool == 0 && task->waiting_user == 0) {
- tasks.erase(p_task_id);
- task_allocator.free(task);
+
+ if (task_to_process) {
+ _process_task(task_to_process);
+ }
+ }
+}
+
+void WorkerThreadPool::yield() {
+ int th_index = get_thread_index();
+ ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread.");
+ _wait_collaboratively(&threads[th_index], ThreadData::YIELDING);
+}
+
+void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
+ task_mutex.lock();
+ Task **taskp = tasks.getptr(p_task_id);
+ if (!taskp) {
+ task_mutex.unlock();
+ ERR_FAIL_MSG("Invalid Task ID.");
+ }
+ Task *task = *taskp;
+ if (task->pool_thread_index == -1) { // Completed or not started yet.
+ if (!task->completed) {
+ // This avoids a race condition where a task is created and yield-over called before it's processed.
+ task->pending_notify_yield_over = true;
}
task_mutex.unlock();
+ return;
}
- return OK;
+ ThreadData &td = threads[task->pool_thread_index];
+ td.yield_is_over = true;
+ td.signaled = true;
+ td.cond_var.notify_one();
+
+ task_mutex.unlock();
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
@@ -557,13 +639,9 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
{
Group *group = *groupp;
- if (flushing_cmd_queue) {
- flushing_cmd_queue->unlock();
- }
+ _unlock_unlockable_mutexes();
group->done_semaphore.wait();
- if (flushing_cmd_queue) {
- flushing_cmd_queue->lock();
- }
+ _lock_unlockable_mutexes();
uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later.
@@ -587,16 +665,41 @@ int WorkerThreadPool::get_thread_index() {
return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1;
}
-void WorkerThreadPool::thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue) {
- ERR_FAIL_COND(flushing_cmd_queue != nullptr);
- flushing_cmd_queue = p_queue;
+#ifdef THREADS_ENABLED
+uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) {
+ return _thread_enter_unlock_allowance_zone(p_mutex, false);
}
-void WorkerThreadPool::thread_exit_command_queue_mt_flush() {
- ERR_FAIL_NULL(flushing_cmd_queue);
- flushing_cmd_queue = nullptr;
+uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) {
+ return _thread_enter_unlock_allowance_zone(p_mutex, true);
}
+uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) {
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlikely((unlockable_mutexes[i] & ~1) == (uintptr_t)p_mutex)) {
+ // Already registered in the current thread.
+ return UINT32_MAX;
+ }
+ if (!unlockable_mutexes[i]) {
+ unlockable_mutexes[i] = (uintptr_t)p_mutex;
+ if (p_is_binary) {
+ unlockable_mutexes[i] |= 1;
+ }
+ return i;
+ }
+ }
+ ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug.");
+}
+
+void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {
+ if (p_zone_id == UINT32_MAX) {
+ return;
+ }
+ DEV_ASSERT(unlockable_mutexes[p_zone_id]);
+ unlockable_mutexes[p_zone_id] = 0;
+}
+#endif
+
void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) {
ERR_FAIL_COND(threads.size() > 0);
if (p_thread_count < 0) {
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index fdddc9a647..8774143abf 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -41,8 +41,6 @@
#include "core/templates/rid.h"
#include "core/templates/safe_refcount.h"
-class CommandQueueMT;
-
class WorkerThreadPool : public Object {
GDCLASS(WorkerThreadPool, Object)
public:
@@ -81,7 +79,8 @@ private:
void *native_func_userdata = nullptr;
String description;
Semaphore done_semaphore; // For user threads awaiting.
- bool completed = false;
+ bool completed : 1;
+ bool pending_notify_yield_over : 1;
Group *group = nullptr;
SelfList<Task> task_elem;
uint32_t waiting_pool = 0;
@@ -92,6 +91,8 @@ private:
void free_template_userdata();
Task() :
+ completed(false),
+ pending_notify_yield_over(false),
task_elem(this) {}
};
@@ -107,13 +108,21 @@ private:
BinaryMutex task_mutex;
struct ThreadData {
+ static Task *const YIELDING; // Too bad constexpr doesn't work here.
+
uint32_t index = 0;
Thread thread;
- bool ready_for_scripting = false;
- bool signaled = false;
+ bool ready_for_scripting : 1;
+ bool signaled : 1;
+ bool yield_is_over : 1;
Task *current_task = nullptr;
- Task *awaited_task = nullptr; // Null if not awaiting the condition variable. Special value for idle-waiting.
+ Task *awaited_task = nullptr; // Null if not awaiting the condition variable, or special value (YIELDING).
ConditionVariable cond_var;
+
+ ThreadData() :
+ ready_for_scripting(false),
+ signaled(false),
+ yield_is_over(false) {}
};
TightLocalVector<ThreadData> threads;
@@ -152,7 +161,10 @@ private:
static WorkerThreadPool *singleton;
- static thread_local CommandQueueMT *flushing_cmd_queue;
+#ifdef THREADS_ENABLED
+ static const uint32_t MAX_UNLOCKABLE_MUTEXES = 2;
+ static thread_local uintptr_t unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES];
+#endif
TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description);
GroupID _add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description);
@@ -177,6 +189,15 @@ private:
}
};
+ void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task);
+
+#ifdef THREADS_ENABLED
+ static uint32_t _thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary);
+#endif
+
+ void _lock_unlockable_mutexes();
+ void _unlock_unlockable_mutexes();
+
protected:
static void _bind_methods();
@@ -196,6 +217,9 @@ public:
bool is_task_completed(TaskID p_task_id) const;
Error wait_for_task_completion(TaskID p_task_id);
+ void yield();
+ void notify_yield_over(TaskID p_task_id);
+
template <typename C, typename M, typename U>
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
typedef GroupUserData<C, M, U> GroupUD;
@@ -216,8 +240,14 @@ public:
static WorkerThreadPool *get_singleton() { return singleton; }
static int get_thread_index();
- static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue);
- static void thread_exit_command_queue_mt_flush();
+#ifdef THREADS_ENABLED
+ static uint32_t thread_enter_unlock_allowance_zone(Mutex *p_mutex);
+ static uint32_t thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex);
+ static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id);
+#else
+ static uint32_t thread_enter_unlock_allowance_zone(void *p_mutex) { return UINT32_MAX; }
+ static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {}
+#endif
void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3);
void finish();
diff --git a/core/os/main_loop.h b/core/os/main_loop.h
index e48541d074..9c22cbaf3c 100644
--- a/core/os/main_loop.h
+++ b/core/os/main_loop.h
@@ -64,6 +64,7 @@ public:
virtual void initialize();
virtual void iteration_prepare() {}
virtual bool physics_process(double p_time);
+ virtual void iteration_end() {}
virtual bool process(double p_time);
virtual void finalize();
diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp
index 6870c84b49..6c748b1498 100644
--- a/core/os/midi_driver.cpp
+++ b/core/os/midi_driver.cpp
@@ -38,88 +38,167 @@ MIDIDriver *MIDIDriver::get_singleton() {
return singleton;
}
-void MIDIDriver::set_singleton() {
+MIDIDriver::MIDIDriver() {
singleton = this;
}
-void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) {
- Ref<InputEventMIDI> event;
- event.instantiate();
- event->set_device(device_index);
- uint32_t param_position = 1;
-
- if (length >= 1) {
- if (data[0] >= 0xF0) {
- // channel does not apply to system common messages
- event->set_channel(0);
- event->set_message(MIDIMessage(data[0]));
- last_received_message = data[0];
- } else if ((data[0] & 0x80) == 0x00) {
- // running status
- event->set_channel(last_received_message & 0xF);
- event->set_message(MIDIMessage(last_received_message >> 4));
- param_position = 0;
+MIDIDriver::MessageCategory MIDIDriver::Parser::category(uint8_t p_midi_fragment) {
+ if (p_midi_fragment >= 0xf8) {
+ return MessageCategory::RealTime;
+ } else if (p_midi_fragment >= 0xf0) {
+ // System Exclusive begin/end are specified as System Common Category
+ // messages, but we separate them here and give them their own categories
+ // as their behavior is significantly different.
+ if (p_midi_fragment == 0xf0) {
+ return MessageCategory::SysExBegin;
+ } else if (p_midi_fragment == 0xf7) {
+ return MessageCategory::SysExEnd;
+ }
+ return MessageCategory::SystemCommon;
+ } else if (p_midi_fragment >= 0x80) {
+ return MessageCategory::Voice;
+ }
+ return MessageCategory::Data;
+}
+
+MIDIMessage MIDIDriver::Parser::status_to_msg_enum(uint8_t p_status_byte) {
+ if (p_status_byte & 0x80) {
+ if (p_status_byte < 0xf0) {
+ return MIDIMessage(p_status_byte >> 4);
} else {
- event->set_channel(data[0] & 0xF);
- event->set_message(MIDIMessage(data[0] >> 4));
- param_position = 1;
- last_received_message = data[0];
+ return MIDIMessage(p_status_byte);
}
}
+ return MIDIMessage::NONE;
+}
- switch (event->get_message()) {
- case MIDIMessage::AFTERTOUCH:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_pressure(data[param_position + 1]);
- }
- break;
+size_t MIDIDriver::Parser::expected_data(uint8_t p_status_byte) {
+ return expected_data(status_to_msg_enum(p_status_byte));
+}
+size_t MIDIDriver::Parser::expected_data(MIDIMessage p_msg_type) {
+ switch (p_msg_type) {
+ case MIDIMessage::NOTE_OFF:
+ case MIDIMessage::NOTE_ON:
+ case MIDIMessage::AFTERTOUCH:
case MIDIMessage::CONTROL_CHANGE:
- if (length >= 2 + param_position) {
- event->set_controller_number(data[param_position]);
- event->set_controller_value(data[param_position + 1]);
- }
- break;
+ case MIDIMessage::PITCH_BEND:
+ case MIDIMessage::SONG_POSITION_POINTER:
+ return 2;
+ case MIDIMessage::PROGRAM_CHANGE:
+ case MIDIMessage::CHANNEL_PRESSURE:
+ case MIDIMessage::QUARTER_FRAME:
+ case MIDIMessage::SONG_SELECT:
+ return 1;
+ default:
+ return 0;
+ }
+}
- case MIDIMessage::NOTE_ON:
+uint8_t MIDIDriver::Parser::channel(uint8_t p_status_byte) {
+ if (category(p_status_byte) == MessageCategory::Voice) {
+ return p_status_byte & 0x0f;
+ }
+ return 0;
+}
+
+void MIDIDriver::send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data, size_t p_data_len) {
+ const MIDIMessage msg = Parser::status_to_msg_enum(p_status);
+ ERR_FAIL_COND(p_data_len < Parser::expected_data(msg));
+
+ Ref<InputEventMIDI> event;
+ event.instantiate();
+ event->set_device(p_device_index);
+ event->set_channel(Parser::channel(p_status));
+ event->set_message(msg);
+ switch (msg) {
case MIDIMessage::NOTE_OFF:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_velocity(data[param_position + 1]);
- }
+ case MIDIMessage::NOTE_ON:
+ event->set_pitch(p_data[0]);
+ event->set_velocity(p_data[1]);
break;
-
- case MIDIMessage::PITCH_BEND:
- if (length >= 2 + param_position) {
- event->set_pitch((data[param_position + 1] << 7) | data[param_position]);
- }
+ case MIDIMessage::AFTERTOUCH:
+ event->set_pitch(p_data[0]);
+ event->set_pressure(p_data[1]);
+ break;
+ case MIDIMessage::CONTROL_CHANGE:
+ event->set_controller_number(p_data[0]);
+ event->set_controller_value(p_data[1]);
break;
-
case MIDIMessage::PROGRAM_CHANGE:
- if (length >= 1 + param_position) {
- event->set_instrument(data[param_position]);
- }
+ event->set_instrument(p_data[0]);
break;
-
case MIDIMessage::CHANNEL_PRESSURE:
- if (length >= 1 + param_position) {
- event->set_pressure(data[param_position]);
- }
+ event->set_pressure(p_data[0]);
+ break;
+ case MIDIMessage::PITCH_BEND:
+ event->set_pitch((p_data[1] << 7) | p_data[0]);
break;
+ // QUARTER_FRAME, SONG_POSITION_POINTER, and SONG_SELECT not yet implemented.
default:
break;
}
-
- Input *id = Input::get_singleton();
- id->parse_input_event(event);
+ Input::get_singleton()->parse_input_event(event);
}
-PackedStringArray MIDIDriver::get_connected_inputs() {
- PackedStringArray list;
- return list;
+void MIDIDriver::Parser::parse_fragment(uint8_t p_fragment) {
+ switch (category(p_fragment)) {
+ case MessageCategory::RealTime:
+ // Real-Time messages are single byte messages that can
+ // occur at any point and do not interrupt other messages.
+ // We pass them straight through.
+ MIDIDriver::send_event(device_index, p_fragment);
+ break;
+
+ case MessageCategory::SysExBegin:
+ status_byte = p_fragment;
+ skipping_sys_ex = true;
+ break;
+
+ case MessageCategory::SysExEnd:
+ status_byte = 0;
+ skipping_sys_ex = false;
+ break;
+
+ case MessageCategory::Voice:
+ case MessageCategory::SystemCommon:
+ skipping_sys_ex = false; // If we were in SysEx, assume it was aborted.
+ received_data_len = 0;
+ status_byte = 0;
+ ERR_FAIL_COND(expected_data(p_fragment) > DATA_BUFFER_SIZE);
+ if (expected_data(p_fragment) == 0) {
+ // No data bytes needed, post it now.
+ MIDIDriver::send_event(device_index, p_fragment);
+ } else {
+ status_byte = p_fragment;
+ }
+ break;
+
+ case MessageCategory::Data:
+ // We don't currently process SysEx messages, so ignore their data.
+ if (!skipping_sys_ex) {
+ const size_t expected = expected_data(status_byte);
+ if (received_data_len < expected) {
+ data_buffer[received_data_len] = p_fragment;
+ received_data_len++;
+ if (received_data_len == expected) {
+ MIDIDriver::send_event(device_index, status_byte,
+ data_buffer, expected);
+ received_data_len = 0;
+ // Voice messages can use 'running status', sending further
+ // messages without resending their status byte.
+ // For other messages types we clear the cached status byte.
+ if (category(status_byte) != MessageCategory::Voice) {
+ status_byte = 0;
+ }
+ }
+ }
+ }
+ break;
+ }
}
-MIDIDriver::MIDIDriver() {
- set_singleton();
+PackedStringArray MIDIDriver::get_connected_inputs() const {
+ return connected_input_names;
}
diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h
index cad3d8189e..ddce63f9c8 100644
--- a/core/os/midi_driver.h
+++ b/core/os/midi_driver.h
@@ -42,19 +42,73 @@ class MIDIDriver {
static MIDIDriver *singleton;
static uint8_t last_received_message;
+protected:
+ // Categories of message for parser logic.
+ enum class MessageCategory {
+ Data,
+ Voice,
+ SysExBegin,
+ SystemCommon, // excluding System Exclusive Begin/End
+ SysExEnd,
+ RealTime,
+ };
+
+ // Convert midi data to InputEventMIDI and send it to Input.
+ // p_data_len is the length of the buffer passed at p_data, this must be
+ // at least equal to the data required by the passed message type, but
+ // may be larger. Only the required data will be read.
+ static void send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data = nullptr, size_t p_data_len = 0);
+
+ class Parser {
+ public:
+ Parser() = default;
+ Parser(int p_device_index) :
+ device_index{ p_device_index } {}
+ virtual ~Parser() = default;
+
+ // Push a byte of MIDI stream. Any completed messages will be
+ // forwarded to MIDIDriver::send_event.
+ void parse_fragment(uint8_t p_fragment);
+
+ static MessageCategory category(uint8_t p_midi_fragment);
+
+ // If the byte is a Voice Message status byte return the contained
+ // channel number, otherwise zero.
+ static uint8_t channel(uint8_t p_status_byte);
+
+ // If the byte is a status byte for a message with a fixed number of
+ // additional data bytes, return the number expected, otherwise zero.
+ static size_t expected_data(uint8_t p_status_byte);
+ static size_t expected_data(MIDIMessage p_msg_type);
+
+ // If the fragment is a status byte return the message type
+ // represented, otherwise MIDIMessage::NONE.
+ static MIDIMessage status_to_msg_enum(uint8_t p_status_byte);
+
+ private:
+ int device_index = 0;
+
+ static constexpr size_t DATA_BUFFER_SIZE = 2;
+
+ uint8_t status_byte = 0;
+ uint8_t data_buffer[DATA_BUFFER_SIZE] = { 0 };
+ size_t received_data_len = 0;
+ bool skipping_sys_ex = false;
+ };
+
+ PackedStringArray connected_input_names;
+
public:
static MIDIDriver *get_singleton();
- void set_singleton();
+
+ MIDIDriver();
+ virtual ~MIDIDriver() = default;
virtual Error open() = 0;
virtual void close() = 0;
- virtual PackedStringArray get_connected_inputs();
-
- static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length);
-
- MIDIDriver();
- virtual ~MIDIDriver() {}
+ PackedStringArray get_connected_inputs() const;
};
#endif // MIDI_DRIVER_H
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 8582888740..642de11a9f 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -247,7 +247,10 @@ String OS::get_safe_dir_name(const String &p_dir_name, bool p_allow_paths) const
for (int i = 0; i < invalid_chars.size(); i++) {
safe_dir_name = safe_dir_name.replace(invalid_chars[i], "-");
}
- return safe_dir_name;
+
+ // Trim trailing periods from the returned value as it's not valid for folder names on Windows.
+ // This check is still applied on non-Windows platforms so the returned value is consistent across platforms.
+ return safe_dir_name.rstrip(".");
}
// Path to data, config, cache, etc. OS-specific folders
@@ -398,6 +401,11 @@ bool OS::has_feature(const String &p_feature) {
if (p_feature == "editor") {
return true;
}
+ if (p_feature == "editor_hint") {
+ return _in_editor;
+ } else if (p_feature == "editor_runtime") {
+ return !_in_editor;
+ }
#else
if (p_feature == "template") {
return true;
@@ -508,6 +516,10 @@ bool OS::has_feature(const String &p_feature) {
if (p_feature == "threads") {
return true;
}
+#else
+ if (p_feature == "nothreads") {
+ return true;
+ }
#endif
if (_check_internal_feature_support(p_feature)) {
diff --git a/core/os/os.h b/core/os/os.h
index 3827bb273a..91e0ce9379 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -63,6 +63,7 @@ class OS {
bool _stdout_enabled = true;
bool _stderr_enabled = true;
bool _writing_movie = false;
+ bool _in_editor = false;
CompositeLogger *_logger = nullptr;
@@ -153,7 +154,14 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) { return ERR_UNAVAILABLE; }
+ struct GDExtensionData {
+ bool also_set_library_path = false;
+ String *r_resolved_path = nullptr;
+ bool generate_temp_files = false;
+ PackedStringArray *library_dependencies = nullptr;
+ };
+
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) { return ERR_UNAVAILABLE; }
virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; }
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) { return ERR_UNAVAILABLE; }
@@ -176,7 +184,8 @@ public:
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;
- virtual void vibrate_handheld(int p_duration_ms = 500) {}
+ virtual int get_process_exit_code(const ProcessID &p_pid) const = 0;
+ virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) {}
virtual Error shell_open(const String &p_uri);
virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder = true);
@@ -319,8 +328,6 @@ public:
virtual void benchmark_end_measure(const String &p_context, const String &p_what);
virtual void benchmark_dump();
- virtual void process_and_drop_events() {}
-
virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path);
enum PreferredTextureFormat {
diff --git a/core/os/shared_object.h b/core/os/shared_object.h
new file mode 100644
index 0000000000..88423bed13
--- /dev/null
+++ b/core/os/shared_object.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* shared_object.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 SHARED_OBJECT_H
+#define SHARED_OBJECT_H
+
+#include "core/string/ustring.h"
+#include "core/templates/vector.h"
+
+struct SharedObject {
+ String path;
+ Vector<String> tags;
+ String target;
+
+ SharedObject(const String &p_path, const Vector<String> &p_tags, const String &p_target) :
+ path(p_path),
+ tags(p_tags),
+ target(p_target) {
+ }
+
+ SharedObject() {}
+};
+
+#endif // SHARED_OBJECT_H
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 4c1ed8a69a..c866ff0415 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -33,7 +33,6 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/core_bind.h"
-#include "core/core_string_names.h"
#include "core/crypto/aes_context.h"
#include "core/crypto/crypto.h"
#include "core/crypto/hashing_context.h"
@@ -80,6 +79,7 @@
#include "core/os/time.h"
#include "core/string/optimized_translation.h"
#include "core/string/translation.h"
+#include "core/string/translation_server.h"
static Ref<ResourceFormatSaverBinary> resource_saver_binary;
static Ref<ResourceFormatLoaderBinary> resource_loader_binary;
@@ -445,8 +445,8 @@ void unregister_core_types() {
unregister_global_constants();
- ClassDB::cleanup();
ResourceCache::clear();
+ ClassDB::cleanup();
CoreStringNames::free();
StringName::cleanup();
diff --git a/core/string/char_range.inc b/core/string/char_range.inc
index b7d6bbdb61..2b081b96de 100644
--- a/core/string/char_range.inc
+++ b/core/string/char_range.inc
@@ -2761,4 +2761,667 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1e922, 0x1e943 },
};
+inline constexpr CharRange unicode_letter[] = {
+ { 0x41, 0x5a },
+ { 0x61, 0x7a },
+ { 0xaa, 0xaa },
+ { 0xb5, 0xb5 },
+ { 0xba, 0xba },
+ { 0xc0, 0xd6 },
+ { 0xd8, 0xf6 },
+ { 0xf8, 0x2c1 },
+ { 0x2c6, 0x2d1 },
+ { 0x2e0, 0x2e4 },
+ { 0x2ec, 0x2ec },
+ { 0x2ee, 0x2ee },
+ { 0x370, 0x374 },
+ { 0x376, 0x377 },
+ { 0x37a, 0x37d },
+ { 0x37f, 0x37f },
+ { 0x386, 0x386 },
+ { 0x388, 0x38a },
+ { 0x38c, 0x38c },
+ { 0x38e, 0x3a1 },
+ { 0x3a3, 0x3f5 },
+ { 0x3f7, 0x481 },
+ { 0x48a, 0x52f },
+ { 0x531, 0x556 },
+ { 0x559, 0x559 },
+ { 0x560, 0x588 },
+ { 0x5d0, 0x5ea },
+ { 0x5ef, 0x5f2 },
+ { 0x620, 0x64a },
+ { 0x66e, 0x66f },
+ { 0x671, 0x6d3 },
+ { 0x6d5, 0x6d5 },
+ { 0x6e5, 0x6e6 },
+ { 0x6ee, 0x6ef },
+ { 0x6fa, 0x6fc },
+ { 0x6ff, 0x6ff },
+ { 0x710, 0x710 },
+ { 0x712, 0x72f },
+ { 0x74d, 0x7a5 },
+ { 0x7b1, 0x7b1 },
+ { 0x7ca, 0x7ea },
+ { 0x7f4, 0x7f5 },
+ { 0x7fa, 0x7fa },
+ { 0x800, 0x815 },
+ { 0x81a, 0x81a },
+ { 0x824, 0x824 },
+ { 0x828, 0x828 },
+ { 0x840, 0x858 },
+ { 0x860, 0x86a },
+ { 0x870, 0x887 },
+ { 0x889, 0x88e },
+ { 0x8a0, 0x8c9 },
+ { 0x904, 0x939 },
+ { 0x93d, 0x93d },
+ { 0x950, 0x950 },
+ { 0x958, 0x961 },
+ { 0x971, 0x980 },
+ { 0x985, 0x98c },
+ { 0x98f, 0x990 },
+ { 0x993, 0x9a8 },
+ { 0x9aa, 0x9b0 },
+ { 0x9b2, 0x9b2 },
+ { 0x9b6, 0x9b9 },
+ { 0x9bd, 0x9bd },
+ { 0x9ce, 0x9ce },
+ { 0x9dc, 0x9dd },
+ { 0x9df, 0x9e1 },
+ { 0x9f0, 0x9f1 },
+ { 0x9fc, 0x9fc },
+ { 0xa05, 0xa0a },
+ { 0xa0f, 0xa10 },
+ { 0xa13, 0xa28 },
+ { 0xa2a, 0xa30 },
+ { 0xa32, 0xa33 },
+ { 0xa35, 0xa36 },
+ { 0xa38, 0xa39 },
+ { 0xa59, 0xa5c },
+ { 0xa5e, 0xa5e },
+ { 0xa72, 0xa74 },
+ { 0xa85, 0xa8d },
+ { 0xa8f, 0xa91 },
+ { 0xa93, 0xaa8 },
+ { 0xaaa, 0xab0 },
+ { 0xab2, 0xab3 },
+ { 0xab5, 0xab9 },
+ { 0xabd, 0xabd },
+ { 0xad0, 0xad0 },
+ { 0xae0, 0xae1 },
+ { 0xaf9, 0xaf9 },
+ { 0xb05, 0xb0c },
+ { 0xb0f, 0xb10 },
+ { 0xb13, 0xb28 },
+ { 0xb2a, 0xb30 },
+ { 0xb32, 0xb33 },
+ { 0xb35, 0xb39 },
+ { 0xb3d, 0xb3d },
+ { 0xb5c, 0xb5d },
+ { 0xb5f, 0xb61 },
+ { 0xb71, 0xb71 },
+ { 0xb83, 0xb83 },
+ { 0xb85, 0xb8a },
+ { 0xb8e, 0xb90 },
+ { 0xb92, 0xb95 },
+ { 0xb99, 0xb9a },
+ { 0xb9c, 0xb9c },
+ { 0xb9e, 0xb9f },
+ { 0xba3, 0xba4 },
+ { 0xba8, 0xbaa },
+ { 0xbae, 0xbb9 },
+ { 0xbd0, 0xbd0 },
+ { 0xc05, 0xc0c },
+ { 0xc0e, 0xc10 },
+ { 0xc12, 0xc28 },
+ { 0xc2a, 0xc39 },
+ { 0xc3d, 0xc3d },
+ { 0xc58, 0xc5a },
+ { 0xc5d, 0xc5d },
+ { 0xc60, 0xc61 },
+ { 0xc80, 0xc80 },
+ { 0xc85, 0xc8c },
+ { 0xc8e, 0xc90 },
+ { 0xc92, 0xca8 },
+ { 0xcaa, 0xcb3 },
+ { 0xcb5, 0xcb9 },
+ { 0xcbd, 0xcbd },
+ { 0xcdd, 0xcde },
+ { 0xce0, 0xce1 },
+ { 0xcf1, 0xcf2 },
+ { 0xd04, 0xd0c },
+ { 0xd0e, 0xd10 },
+ { 0xd12, 0xd3a },
+ { 0xd3d, 0xd3d },
+ { 0xd4e, 0xd4e },
+ { 0xd54, 0xd56 },
+ { 0xd5f, 0xd61 },
+ { 0xd7a, 0xd7f },
+ { 0xd85, 0xd96 },
+ { 0xd9a, 0xdb1 },
+ { 0xdb3, 0xdbb },
+ { 0xdbd, 0xdbd },
+ { 0xdc0, 0xdc6 },
+ { 0xe01, 0xe30 },
+ { 0xe32, 0xe33 },
+ { 0xe40, 0xe46 },
+ { 0xe81, 0xe82 },
+ { 0xe84, 0xe84 },
+ { 0xe86, 0xe8a },
+ { 0xe8c, 0xea3 },
+ { 0xea5, 0xea5 },
+ { 0xea7, 0xeb0 },
+ { 0xeb2, 0xeb3 },
+ { 0xebd, 0xebd },
+ { 0xec0, 0xec4 },
+ { 0xec6, 0xec6 },
+ { 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 },
+ { 0x10a0, 0x10c5 },
+ { 0x10c7, 0x10c7 },
+ { 0x10cd, 0x10cd },
+ { 0x10d0, 0x10fa },
+ { 0x10fc, 0x1248 },
+ { 0x124a, 0x124d },
+ { 0x1250, 0x1256 },
+ { 0x1258, 0x1258 },
+ { 0x125a, 0x125d },
+ { 0x1260, 0x1288 },
+ { 0x128a, 0x128d },
+ { 0x1290, 0x12b0 },
+ { 0x12b2, 0x12b5 },
+ { 0x12b8, 0x12be },
+ { 0x12c0, 0x12c0 },
+ { 0x12c2, 0x12c5 },
+ { 0x12c8, 0x12d6 },
+ { 0x12d8, 0x1310 },
+ { 0x1312, 0x1315 },
+ { 0x1318, 0x135a },
+ { 0x1380, 0x138f },
+ { 0x13a0, 0x13f5 },
+ { 0x13f8, 0x13fd },
+ { 0x1401, 0x166c },
+ { 0x166f, 0x167f },
+ { 0x1681, 0x169a },
+ { 0x16a0, 0x16ea },
+ { 0x16f1, 0x16f8 },
+ { 0x1700, 0x1711 },
+ { 0x171f, 0x1731 },
+ { 0x1740, 0x1751 },
+ { 0x1760, 0x176c },
+ { 0x176e, 0x1770 },
+ { 0x1780, 0x17b3 },
+ { 0x17d7, 0x17d7 },
+ { 0x17dc, 0x17dc },
+ { 0x1820, 0x1878 },
+ { 0x1880, 0x1884 },
+ { 0x1887, 0x18a8 },
+ { 0x18aa, 0x18aa },
+ { 0x18b0, 0x18f5 },
+ { 0x1900, 0x191e },
+ { 0x1950, 0x196d },
+ { 0x1970, 0x1974 },
+ { 0x1980, 0x19ab },
+ { 0x19b0, 0x19c9 },
+ { 0x1a00, 0x1a16 },
+ { 0x1a20, 0x1a54 },
+ { 0x1aa7, 0x1aa7 },
+ { 0x1b05, 0x1b33 },
+ { 0x1b45, 0x1b4c },
+ { 0x1b83, 0x1ba0 },
+ { 0x1bae, 0x1baf },
+ { 0x1bba, 0x1be5 },
+ { 0x1c00, 0x1c23 },
+ { 0x1c4d, 0x1c4f },
+ { 0x1c5a, 0x1c7d },
+ { 0x1c80, 0x1c88 },
+ { 0x1c90, 0x1cba },
+ { 0x1cbd, 0x1cbf },
+ { 0x1ce9, 0x1cec },
+ { 0x1cee, 0x1cf3 },
+ { 0x1cf5, 0x1cf6 },
+ { 0x1cfa, 0x1cfa },
+ { 0x1d00, 0x1dbf },
+ { 0x1e00, 0x1f15 },
+ { 0x1f18, 0x1f1d },
+ { 0x1f20, 0x1f45 },
+ { 0x1f48, 0x1f4d },
+ { 0x1f50, 0x1f57 },
+ { 0x1f59, 0x1f59 },
+ { 0x1f5b, 0x1f5b },
+ { 0x1f5d, 0x1f5d },
+ { 0x1f5f, 0x1f7d },
+ { 0x1f80, 0x1fb4 },
+ { 0x1fb6, 0x1fbc },
+ { 0x1fbe, 0x1fbe },
+ { 0x1fc2, 0x1fc4 },
+ { 0x1fc6, 0x1fcc },
+ { 0x1fd0, 0x1fd3 },
+ { 0x1fd6, 0x1fdb },
+ { 0x1fe0, 0x1fec },
+ { 0x1ff2, 0x1ff4 },
+ { 0x1ff6, 0x1ffc },
+ { 0x2071, 0x2071 },
+ { 0x207f, 0x207f },
+ { 0x2090, 0x209c },
+ { 0x2102, 0x2102 },
+ { 0x2107, 0x2107 },
+ { 0x210a, 0x2113 },
+ { 0x2115, 0x2115 },
+ { 0x2119, 0x211d },
+ { 0x2124, 0x2124 },
+ { 0x2126, 0x2126 },
+ { 0x2128, 0x2128 },
+ { 0x212a, 0x212d },
+ { 0x212f, 0x2139 },
+ { 0x213c, 0x213f },
+ { 0x2145, 0x2149 },
+ { 0x214e, 0x214e },
+ { 0x2183, 0x2184 },
+ { 0x2c00, 0x2ce4 },
+ { 0x2ceb, 0x2cee },
+ { 0x2cf2, 0x2cf3 },
+ { 0x2d00, 0x2d25 },
+ { 0x2d27, 0x2d27 },
+ { 0x2d2d, 0x2d2d },
+ { 0x2d30, 0x2d67 },
+ { 0x2d6f, 0x2d6f },
+ { 0x2d80, 0x2d96 },
+ { 0x2da0, 0x2da6 },
+ { 0x2da8, 0x2dae },
+ { 0x2db0, 0x2db6 },
+ { 0x2db8, 0x2dbe },
+ { 0x2dc0, 0x2dc6 },
+ { 0x2dc8, 0x2dce },
+ { 0x2dd0, 0x2dd6 },
+ { 0x2dd8, 0x2dde },
+ { 0x2e2f, 0x2e2f },
+ { 0x3005, 0x3006 },
+ { 0x3031, 0x3035 },
+ { 0x303b, 0x303c },
+ { 0x3041, 0x3096 },
+ { 0x309d, 0x309f },
+ { 0x30a1, 0x30fa },
+ { 0x30fc, 0x30ff },
+ { 0x3105, 0x312f },
+ { 0x3131, 0x318e },
+ { 0x31a0, 0x31bf },
+ { 0x31f0, 0x31ff },
+ { 0x3400, 0x4dbf },
+ { 0x4e00, 0xa48c },
+ { 0xa4d0, 0xa4fd },
+ { 0xa500, 0xa60c },
+ { 0xa610, 0xa61f },
+ { 0xa62a, 0xa62b },
+ { 0xa640, 0xa66e },
+ { 0xa67f, 0xa69d },
+ { 0xa6a0, 0xa6e5 },
+ { 0xa717, 0xa71f },
+ { 0xa722, 0xa788 },
+ { 0xa78b, 0xa7ca },
+ { 0xa7d0, 0xa7d1 },
+ { 0xa7d3, 0xa7d3 },
+ { 0xa7d5, 0xa7d9 },
+ { 0xa7f2, 0xa801 },
+ { 0xa803, 0xa805 },
+ { 0xa807, 0xa80a },
+ { 0xa80c, 0xa822 },
+ { 0xa840, 0xa873 },
+ { 0xa882, 0xa8b3 },
+ { 0xa8f2, 0xa8f7 },
+ { 0xa8fb, 0xa8fb },
+ { 0xa8fd, 0xa8fe },
+ { 0xa90a, 0xa925 },
+ { 0xa930, 0xa946 },
+ { 0xa960, 0xa97c },
+ { 0xa984, 0xa9b2 },
+ { 0xa9cf, 0xa9cf },
+ { 0xa9e0, 0xa9e4 },
+ { 0xa9e6, 0xa9ef },
+ { 0xa9fa, 0xa9fe },
+ { 0xaa00, 0xaa28 },
+ { 0xaa40, 0xaa42 },
+ { 0xaa44, 0xaa4b },
+ { 0xaa60, 0xaa76 },
+ { 0xaa7a, 0xaa7a },
+ { 0xaa7e, 0xaaaf },
+ { 0xaab1, 0xaab1 },
+ { 0xaab5, 0xaab6 },
+ { 0xaab9, 0xaabd },
+ { 0xaac0, 0xaac0 },
+ { 0xaac2, 0xaac2 },
+ { 0xaadb, 0xaadd },
+ { 0xaae0, 0xaaea },
+ { 0xaaf2, 0xaaf4 },
+ { 0xab01, 0xab06 },
+ { 0xab09, 0xab0e },
+ { 0xab11, 0xab16 },
+ { 0xab20, 0xab26 },
+ { 0xab28, 0xab2e },
+ { 0xab30, 0xab5a },
+ { 0xab5c, 0xab69 },
+ { 0xab70, 0xabe2 },
+ { 0xac00, 0xd7a3 },
+ { 0xd7b0, 0xd7c6 },
+ { 0xd7cb, 0xd7fb },
+ { 0xf900, 0xfa6d },
+ { 0xfa70, 0xfad9 },
+ { 0xfb00, 0xfb06 },
+ { 0xfb13, 0xfb17 },
+ { 0xfb1d, 0xfb1d },
+ { 0xfb1f, 0xfb28 },
+ { 0xfb2a, 0xfb36 },
+ { 0xfb38, 0xfb3c },
+ { 0xfb3e, 0xfb3e },
+ { 0xfb40, 0xfb41 },
+ { 0xfb43, 0xfb44 },
+ { 0xfb46, 0xfbb1 },
+ { 0xfbd3, 0xfd3d },
+ { 0xfd50, 0xfd8f },
+ { 0xfd92, 0xfdc7 },
+ { 0xfdf0, 0xfdfb },
+ { 0xfe70, 0xfe74 },
+ { 0xfe76, 0xfefc },
+ { 0xff21, 0xff3a },
+ { 0xff41, 0xff5a },
+ { 0xff66, 0xffbe },
+ { 0xffc2, 0xffc7 },
+ { 0xffca, 0xffcf },
+ { 0xffd2, 0xffd7 },
+ { 0xffda, 0xffdc },
+ { 0x10000, 0x1000b },
+ { 0x1000d, 0x10026 },
+ { 0x10028, 0x1003a },
+ { 0x1003c, 0x1003d },
+ { 0x1003f, 0x1004d },
+ { 0x10050, 0x1005d },
+ { 0x10080, 0x100fa },
+ { 0x10280, 0x1029c },
+ { 0x102a0, 0x102d0 },
+ { 0x10300, 0x1031f },
+ { 0x1032d, 0x10340 },
+ { 0x10342, 0x10349 },
+ { 0x10350, 0x10375 },
+ { 0x10380, 0x1039d },
+ { 0x103a0, 0x103c3 },
+ { 0x103c8, 0x103cf },
+ { 0x10400, 0x1049d },
+ { 0x104b0, 0x104d3 },
+ { 0x104d8, 0x104fb },
+ { 0x10500, 0x10527 },
+ { 0x10530, 0x10563 },
+ { 0x10570, 0x1057a },
+ { 0x1057c, 0x1058a },
+ { 0x1058c, 0x10592 },
+ { 0x10594, 0x10595 },
+ { 0x10597, 0x105a1 },
+ { 0x105a3, 0x105b1 },
+ { 0x105b3, 0x105b9 },
+ { 0x105bb, 0x105bc },
+ { 0x10600, 0x10736 },
+ { 0x10740, 0x10755 },
+ { 0x10760, 0x10767 },
+ { 0x10780, 0x10785 },
+ { 0x10787, 0x107b0 },
+ { 0x107b2, 0x107ba },
+ { 0x10800, 0x10805 },
+ { 0x10808, 0x10808 },
+ { 0x1080a, 0x10835 },
+ { 0x10837, 0x10838 },
+ { 0x1083c, 0x1083c },
+ { 0x1083f, 0x10855 },
+ { 0x10860, 0x10876 },
+ { 0x10880, 0x1089e },
+ { 0x108e0, 0x108f2 },
+ { 0x108f4, 0x108f5 },
+ { 0x10900, 0x10915 },
+ { 0x10920, 0x10939 },
+ { 0x10980, 0x109b7 },
+ { 0x109be, 0x109bf },
+ { 0x10a00, 0x10a00 },
+ { 0x10a10, 0x10a13 },
+ { 0x10a15, 0x10a17 },
+ { 0x10a19, 0x10a35 },
+ { 0x10a60, 0x10a7c },
+ { 0x10a80, 0x10a9c },
+ { 0x10ac0, 0x10ac7 },
+ { 0x10ac9, 0x10ae4 },
+ { 0x10b00, 0x10b35 },
+ { 0x10b40, 0x10b55 },
+ { 0x10b60, 0x10b72 },
+ { 0x10b80, 0x10b91 },
+ { 0x10c00, 0x10c48 },
+ { 0x10c80, 0x10cb2 },
+ { 0x10cc0, 0x10cf2 },
+ { 0x10d00, 0x10d23 },
+ { 0x10e80, 0x10ea9 },
+ { 0x10eb0, 0x10eb1 },
+ { 0x10f00, 0x10f1c },
+ { 0x10f27, 0x10f27 },
+ { 0x10f30, 0x10f45 },
+ { 0x10f70, 0x10f81 },
+ { 0x10fb0, 0x10fc4 },
+ { 0x10fe0, 0x10ff6 },
+ { 0x11003, 0x11037 },
+ { 0x11071, 0x11072 },
+ { 0x11075, 0x11075 },
+ { 0x11083, 0x110af },
+ { 0x110d0, 0x110e8 },
+ { 0x11103, 0x11126 },
+ { 0x11144, 0x11144 },
+ { 0x11147, 0x11147 },
+ { 0x11150, 0x11172 },
+ { 0x11176, 0x11176 },
+ { 0x11183, 0x111b2 },
+ { 0x111c1, 0x111c4 },
+ { 0x111da, 0x111da },
+ { 0x111dc, 0x111dc },
+ { 0x11200, 0x11211 },
+ { 0x11213, 0x1122b },
+ { 0x1123f, 0x11240 },
+ { 0x11280, 0x11286 },
+ { 0x11288, 0x11288 },
+ { 0x1128a, 0x1128d },
+ { 0x1128f, 0x1129d },
+ { 0x1129f, 0x112a8 },
+ { 0x112b0, 0x112de },
+ { 0x11305, 0x1130c },
+ { 0x1130f, 0x11310 },
+ { 0x11313, 0x11328 },
+ { 0x1132a, 0x11330 },
+ { 0x11332, 0x11333 },
+ { 0x11335, 0x11339 },
+ { 0x1133d, 0x1133d },
+ { 0x11350, 0x11350 },
+ { 0x1135d, 0x11361 },
+ { 0x11400, 0x11434 },
+ { 0x11447, 0x1144a },
+ { 0x1145f, 0x11461 },
+ { 0x11480, 0x114af },
+ { 0x114c4, 0x114c5 },
+ { 0x114c7, 0x114c7 },
+ { 0x11580, 0x115ae },
+ { 0x115d8, 0x115db },
+ { 0x11600, 0x1162f },
+ { 0x11644, 0x11644 },
+ { 0x11680, 0x116aa },
+ { 0x116b8, 0x116b8 },
+ { 0x11700, 0x1171a },
+ { 0x11740, 0x11746 },
+ { 0x11800, 0x1182b },
+ { 0x118a0, 0x118df },
+ { 0x118ff, 0x11906 },
+ { 0x11909, 0x11909 },
+ { 0x1190c, 0x11913 },
+ { 0x11915, 0x11916 },
+ { 0x11918, 0x1192f },
+ { 0x1193f, 0x1193f },
+ { 0x11941, 0x11941 },
+ { 0x119a0, 0x119a7 },
+ { 0x119aa, 0x119d0 },
+ { 0x119e1, 0x119e1 },
+ { 0x119e3, 0x119e3 },
+ { 0x11a00, 0x11a00 },
+ { 0x11a0b, 0x11a32 },
+ { 0x11a3a, 0x11a3a },
+ { 0x11a50, 0x11a50 },
+ { 0x11a5c, 0x11a89 },
+ { 0x11a9d, 0x11a9d },
+ { 0x11ab0, 0x11af8 },
+ { 0x11c00, 0x11c08 },
+ { 0x11c0a, 0x11c2e },
+ { 0x11c40, 0x11c40 },
+ { 0x11c72, 0x11c8f },
+ { 0x11d00, 0x11d06 },
+ { 0x11d08, 0x11d09 },
+ { 0x11d0b, 0x11d30 },
+ { 0x11d46, 0x11d46 },
+ { 0x11d60, 0x11d65 },
+ { 0x11d67, 0x11d68 },
+ { 0x11d6a, 0x11d89 },
+ { 0x11d98, 0x11d98 },
+ { 0x11ee0, 0x11ef2 },
+ { 0x11f02, 0x11f02 },
+ { 0x11f04, 0x11f10 },
+ { 0x11f12, 0x11f33 },
+ { 0x11fb0, 0x11fb0 },
+ { 0x12000, 0x12399 },
+ { 0x12480, 0x12543 },
+ { 0x12f90, 0x12ff0 },
+ { 0x13000, 0x1342f },
+ { 0x13441, 0x13446 },
+ { 0x14400, 0x14646 },
+ { 0x16800, 0x16a38 },
+ { 0x16a40, 0x16a5e },
+ { 0x16a70, 0x16abe },
+ { 0x16ad0, 0x16aed },
+ { 0x16b00, 0x16b2f },
+ { 0x16b40, 0x16b43 },
+ { 0x16b63, 0x16b77 },
+ { 0x16b7d, 0x16b8f },
+ { 0x16e40, 0x16e7f },
+ { 0x16f00, 0x16f4a },
+ { 0x16f50, 0x16f50 },
+ { 0x16f93, 0x16f9f },
+ { 0x16fe0, 0x16fe1 },
+ { 0x16fe3, 0x16fe3 },
+ { 0x17000, 0x187f7 },
+ { 0x18800, 0x18cd5 },
+ { 0x18d00, 0x18d08 },
+ { 0x1aff0, 0x1aff3 },
+ { 0x1aff5, 0x1affb },
+ { 0x1affd, 0x1affe },
+ { 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
+ { 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
+ { 0x1b164, 0x1b167 },
+ { 0x1b170, 0x1b2fb },
+ { 0x1bc00, 0x1bc6a },
+ { 0x1bc70, 0x1bc7c },
+ { 0x1bc80, 0x1bc88 },
+ { 0x1bc90, 0x1bc99 },
+ { 0x1d400, 0x1d454 },
+ { 0x1d456, 0x1d49c },
+ { 0x1d49e, 0x1d49f },
+ { 0x1d4a2, 0x1d4a2 },
+ { 0x1d4a5, 0x1d4a6 },
+ { 0x1d4a9, 0x1d4ac },
+ { 0x1d4ae, 0x1d4b9 },
+ { 0x1d4bb, 0x1d4bb },
+ { 0x1d4bd, 0x1d4c3 },
+ { 0x1d4c5, 0x1d505 },
+ { 0x1d507, 0x1d50a },
+ { 0x1d50d, 0x1d514 },
+ { 0x1d516, 0x1d51c },
+ { 0x1d51e, 0x1d539 },
+ { 0x1d53b, 0x1d53e },
+ { 0x1d540, 0x1d544 },
+ { 0x1d546, 0x1d546 },
+ { 0x1d54a, 0x1d550 },
+ { 0x1d552, 0x1d6a5 },
+ { 0x1d6a8, 0x1d6c0 },
+ { 0x1d6c2, 0x1d6da },
+ { 0x1d6dc, 0x1d6fa },
+ { 0x1d6fc, 0x1d714 },
+ { 0x1d716, 0x1d734 },
+ { 0x1d736, 0x1d74e },
+ { 0x1d750, 0x1d76e },
+ { 0x1d770, 0x1d788 },
+ { 0x1d78a, 0x1d7a8 },
+ { 0x1d7aa, 0x1d7c2 },
+ { 0x1d7c4, 0x1d7cb },
+ { 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
+ { 0x1e100, 0x1e12c },
+ { 0x1e137, 0x1e13d },
+ { 0x1e14e, 0x1e14e },
+ { 0x1e290, 0x1e2ad },
+ { 0x1e2c0, 0x1e2eb },
+ { 0x1e4d0, 0x1e4eb },
+ { 0x1e7e0, 0x1e7e6 },
+ { 0x1e7e8, 0x1e7eb },
+ { 0x1e7ed, 0x1e7ee },
+ { 0x1e7f0, 0x1e7fe },
+ { 0x1e800, 0x1e8c4 },
+ { 0x1e900, 0x1e943 },
+ { 0x1e94b, 0x1e94b },
+ { 0x1ee00, 0x1ee03 },
+ { 0x1ee05, 0x1ee1f },
+ { 0x1ee21, 0x1ee22 },
+ { 0x1ee24, 0x1ee24 },
+ { 0x1ee27, 0x1ee27 },
+ { 0x1ee29, 0x1ee32 },
+ { 0x1ee34, 0x1ee37 },
+ { 0x1ee39, 0x1ee39 },
+ { 0x1ee3b, 0x1ee3b },
+ { 0x1ee42, 0x1ee42 },
+ { 0x1ee47, 0x1ee47 },
+ { 0x1ee49, 0x1ee49 },
+ { 0x1ee4b, 0x1ee4b },
+ { 0x1ee4d, 0x1ee4f },
+ { 0x1ee51, 0x1ee52 },
+ { 0x1ee54, 0x1ee54 },
+ { 0x1ee57, 0x1ee57 },
+ { 0x1ee59, 0x1ee59 },
+ { 0x1ee5b, 0x1ee5b },
+ { 0x1ee5d, 0x1ee5d },
+ { 0x1ee5f, 0x1ee5f },
+ { 0x1ee61, 0x1ee62 },
+ { 0x1ee64, 0x1ee64 },
+ { 0x1ee67, 0x1ee6a },
+ { 0x1ee6c, 0x1ee72 },
+ { 0x1ee74, 0x1ee77 },
+ { 0x1ee79, 0x1ee7c },
+ { 0x1ee7e, 0x1ee7e },
+ { 0x1ee80, 0x1ee89 },
+ { 0x1ee8b, 0x1ee9b },
+ { 0x1eea1, 0x1eea3 },
+ { 0x1eea5, 0x1eea9 },
+ { 0x1eeab, 0x1eebb },
+ { 0x20000, 0x2a6df },
+ { 0x2a700, 0x2b739 },
+ { 0x2b740, 0x2b81d },
+ { 0x2b820, 0x2cea1 },
+ { 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
+ { 0x2f800, 0x2fa1d },
+ { 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
+};
+
#endif // CHAR_RANGE_INC
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
index aa9bc198ca..4acb81253f 100644
--- a/core/string/char_utils.h
+++ b/core/string/char_utils.h
@@ -70,6 +70,10 @@ static _FORCE_INLINE_ bool is_unicode_lower_case(char32_t c) {
BSEARCH_CHAR_RANGE(lowercase_letter);
}
+static _FORCE_INLINE_ bool is_unicode_letter(char32_t c) {
+ BSEARCH_CHAR_RANGE(unicode_letter);
+}
+
#undef BSEARCH_CHAR_RANGE
static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
@@ -92,7 +96,7 @@ static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
return (c == '0' || c == '1');
}
-static _FORCE_INLINE_ bool is_ascii_char(char32_t c) {
+static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
diff --git a/core/string/translation.compat.inc b/core/string/translation.compat.inc
index d792d4a6fc..68bd1831e4 100644
--- a/core/string/translation.compat.inc
+++ b/core/string/translation.compat.inc
@@ -38,9 +38,4 @@ void Translation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
}
-void TranslationServer::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
-}
-
#endif
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 613edd11cd..33d4a1bcde 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -31,14 +31,9 @@
#include "translation.h"
#include "translation.compat.inc"
-#include "core/config/project_settings.h"
-#include "core/io/resource_loader.h"
#include "core/os/os.h"
-#include "core/string/locales.h"
-
-#ifdef TOOLS_ENABLED
-#include "main/main.h"
-#endif
+#include "core/os/thread.h"
+#include "core/string/translation_server.h"
Dictionary Translation::_get_messages() const {
Dictionary d;
@@ -173,915 +168,3 @@ void Translation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
-
-///////////////////////////////////////////////
-
-struct _character_accent_pair {
- const char32_t character;
- const char32_t *accented_character;
-};
-
-static _character_accent_pair _character_to_accented[] = {
- { 'A', U"Å" },
- { 'B', U"ß" },
- { 'C', U"Ç" },
- { 'D', U"Ð" },
- { 'E', U"É" },
- { 'F', U"F́" },
- { 'G', U"Ĝ" },
- { 'H', U"Ĥ" },
- { 'I', U"Ĩ" },
- { 'J', U"Ĵ" },
- { 'K', U"ĸ" },
- { 'L', U"Ł" },
- { 'M', U"Ḿ" },
- { 'N', U"й" },
- { 'O', U"Ö" },
- { 'P', U"Ṕ" },
- { 'Q', U"Q́" },
- { 'R', U"Ř" },
- { 'S', U"Ŝ" },
- { 'T', U"Ŧ" },
- { 'U', U"Ũ" },
- { 'V', U"Ṽ" },
- { 'W', U"Ŵ" },
- { 'X', U"X́" },
- { 'Y', U"Ÿ" },
- { 'Z', U"Ž" },
- { 'a', U"á" },
- { 'b', U"ḅ" },
- { 'c', U"ć" },
- { 'd', U"d́" },
- { 'e', U"é" },
- { 'f', U"f́" },
- { 'g', U"ǵ" },
- { 'h', U"h̀" },
- { 'i', U"í" },
- { 'j', U"ǰ" },
- { 'k', U"ḱ" },
- { 'l', U"ł" },
- { 'm', U"m̀" },
- { 'n', U"ή" },
- { 'o', U"ô" },
- { 'p', U"ṕ" },
- { 'q', U"q́" },
- { 'r', U"ŕ" },
- { 's', U"š" },
- { 't', U"ŧ" },
- { 'u', U"ü" },
- { 'v', U"ṽ" },
- { 'w', U"ŵ" },
- { 'x', U"x́" },
- { 'y', U"ý" },
- { 'z', U"ź" },
-};
-
-Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
-
-HashMap<String, String> TranslationServer::language_map;
-HashMap<String, String> TranslationServer::script_map;
-HashMap<String, String> TranslationServer::locale_rename_map;
-HashMap<String, String> TranslationServer::country_name_map;
-HashMap<String, String> TranslationServer::variant_map;
-HashMap<String, String> TranslationServer::country_rename_map;
-
-void TranslationServer::init_locale_info() {
- // Init locale info.
- language_map.clear();
- int idx = 0;
- while (language_list[idx][0] != nullptr) {
- language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
- idx++;
- }
-
- // Init locale-script map.
- locale_script_info.clear();
- idx = 0;
- while (locale_scripts[idx][0] != nullptr) {
- LocaleScriptInfo info;
- info.name = locale_scripts[idx][0];
- info.script = locale_scripts[idx][1];
- info.default_country = locale_scripts[idx][2];
- Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
- for (int i = 0; i < supported_countries.size(); i++) {
- info.supported_countries.insert(supported_countries[i]);
- }
- locale_script_info.push_back(info);
- idx++;
- }
-
- // Init supported script list.
- script_map.clear();
- idx = 0;
- while (script_list[idx][0] != nullptr) {
- script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
- idx++;
- }
-
- // Init regional variant map.
- variant_map.clear();
- idx = 0;
- while (locale_variants[idx][0] != nullptr) {
- variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
- idx++;
- }
-
- // Init locale renames.
- locale_rename_map.clear();
- idx = 0;
- while (locale_renames[idx][0] != nullptr) {
- if (!String(locale_renames[idx][1]).is_empty()) {
- locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
- }
- idx++;
- }
-
- // Init country names.
- country_name_map.clear();
- idx = 0;
- while (country_names[idx][0] != nullptr) {
- country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
- idx++;
- }
-
- // Init country renames.
- country_rename_map.clear();
- idx = 0;
- while (country_renames[idx][0] != nullptr) {
- if (!String(country_renames[idx][1]).is_empty()) {
- country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
- }
- idx++;
- }
-}
-
-String TranslationServer::standardize_locale(const String &p_locale) const {
- return _standardize_locale(p_locale, false);
-}
-
-String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
- // 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];
- 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];
- }
- 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];
- }
- }
- 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();
- }
- }
- 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();
- }
- }
-
- // Try extract script and variant from the extra part.
- 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";
- break;
- } else if (script_extra[i].to_lower() == "latin") {
- script_name = "Latn";
- break;
- } else if (script_extra[i].to_lower() == "devanagari") {
- script_name = "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();
- }
- }
-
- // 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];
- }
-
- // Handle country renames.
- if (country_rename_map.has(country_name)) {
- country_name = country_rename_map[country_name];
- }
-
- // Remove unsupported script codes.
- if (!script_map.has(script_name)) {
- script_name = "";
- }
-
- // 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;
- break;
- }
- }
- }
- }
- if (!script_name.is_empty() && country_name.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;
- 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;
-}
-
-int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
- String locale_a = _standardize_locale(p_locale_a, true);
- String locale_b = _standardize_locale(p_locale_b, true);
-
- if (locale_a == locale_b) {
- // Exact match.
- 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]) {
- // 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++;
- }
- }
- }
- return matching_elements;
- } else {
- // No match.
- return 0;
- }
-}
-
-String TranslationServer::get_locale_name(const String &p_locale) const {
- String lang_name, script_name, country_name;
- Vector<String> locale_elements = standardize_locale(p_locale).split("_");
- lang_name = 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];
- }
- 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];
- }
- }
- 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];
- }
- }
-
- String name = language_map[lang_name];
- if (!script_name.is_empty()) {
- name = name + " (" + script_map[script_name] + ")";
- }
- if (!country_name.is_empty()) {
- name = name + ", " + country_name_map[country_name];
- }
- return name;
-}
-
-Vector<String> TranslationServer::get_all_languages() const {
- Vector<String> languages;
-
- for (const KeyValue<String, String> &E : language_map) {
- languages.push_back(E.key);
- }
-
- return languages;
-}
-
-String TranslationServer::get_language_name(const String &p_language) const {
- return language_map[p_language];
-}
-
-Vector<String> TranslationServer::get_all_scripts() const {
- Vector<String> scripts;
-
- for (const KeyValue<String, String> &E : script_map) {
- scripts.push_back(E.key);
- }
-
- return scripts;
-}
-
-String TranslationServer::get_script_name(const String &p_script) const {
- return script_map[p_script];
-}
-
-Vector<String> TranslationServer::get_all_countries() const {
- Vector<String> countries;
-
- for (const KeyValue<String, String> &E : country_name_map) {
- countries.push_back(E.key);
- }
-
- return countries;
-}
-
-String TranslationServer::get_country_name(const String &p_country) const {
- return country_name_map[p_country];
-}
-
-void TranslationServer::set_locale(const String &p_locale) {
- String new_locale = standardize_locale(p_locale);
- if (locale == new_locale) {
- return;
- }
-
- locale = new_locale;
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-String TranslationServer::get_locale() const {
- return locale;
-}
-
-PackedStringArray TranslationServer::get_loaded_locales() const {
- PackedStringArray locales;
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
- String l = t->get_locale();
-
- locales.push_back(l);
- }
-
- return locales;
-}
-
-void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
- translations.insert(p_translation);
-}
-
-void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
- translations.erase(p_translation);
-}
-
-Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
- Ref<Translation> res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), nullptr);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- res = t;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return res;
-}
-
-void TranslationServer::clear() {
- translations.clear();
-}
-
-StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
- // Match given message against the translation catalog for the project locale.
-
- if (!enabled) {
- return p_message;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, false);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, false);
- }
-
- if (!res) {
- return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
- }
-
- return pseudolocalization_enabled ? pseudolocalize(res) : res;
-}
-
-StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (!enabled) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
- }
-
- if (!res) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- return res;
-}
-
-StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
- StringName res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), p_message);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- StringName r;
- if (!plural) {
- r = t->get_message(p_message, p_context);
- } else {
- r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
- }
- if (!r) {
- continue;
- }
- res = r;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
-
- return res;
-}
-
-TranslationServer *TranslationServer::singleton = nullptr;
-
-bool TranslationServer::_load_translations(const String &p_from) {
- if (ProjectSettings::get_singleton()->has_setting(p_from)) {
- const Vector<String> &translation_names = GLOBAL_GET(p_from);
-
- int tcount = translation_names.size();
-
- if (tcount) {
- const String *r = translation_names.ptr();
-
- for (int i = 0; i < tcount; i++) {
- Ref<Translation> tr = ResourceLoader::load(r[i]);
- if (tr.is_valid()) {
- add_translation(tr);
- }
- }
- }
- return true;
- }
-
- return false;
-}
-
-void TranslationServer::setup() {
- String test = GLOBAL_DEF("internationalization/locale/test", "");
- test = test.strip_edges();
- if (!test.is_empty()) {
- set_locale(test);
- } else {
- set_locale(OS::get_singleton()->get_locale());
- }
-
- fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
- pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
- pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
- pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
- pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
- pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
- expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
- pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
- pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
-
-#ifdef TOOLS_ENABLED
- ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
-#endif
-}
-
-void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
- tool_translation = p_translation;
-}
-
-Ref<Translation> TranslationServer::get_tool_translation() const {
- return tool_translation;
-}
-
-String TranslationServer::get_tool_locale() {
-#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
- if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
- return tool_translation->get_locale();
- } else {
- return "en";
- }
- } else {
-#else
- {
-#endif
- // Look for best matching loaded translation.
- String best_locale = "en";
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), best_locale);
- String l = t->get_locale();
-
- int score = compare_locales(locale, l);
- if (score > 0 && score >= best_score) {
- best_locale = l;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return best_locale;
- }
-}
-
-StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_message(p_message, p_context);
- if (r) {
- return editor_pseudolocalization ? tool_pseudolocalize(r) : r;
- }
- }
- return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message;
-}
-
-StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
- property_translation = p_translation;
-}
-
-StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
- if (property_translation.is_valid()) {
- StringName r = property_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
- doc_translation = p_translation;
-}
-
-StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
- extractable_translation = p_translation;
-}
-
-StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-bool TranslationServer::is_pseudolocalization_enabled() const {
- return pseudolocalization_enabled;
-}
-
-void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
- pseudolocalization_enabled = p_enabled;
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-void TranslationServer::set_editor_pseudolocalization(bool p_enabled) {
- editor_pseudolocalization = p_enabled;
-}
-
-void TranslationServer::reload_pseudolocalization() {
- pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
- pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
- pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
- pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
- expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
- pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
- pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- int length = message.length();
- if (pseudolocalization_override_enabled) {
- message = get_override_string(message);
- }
-
- if (pseudolocalization_double_vowels_enabled) {
- message = double_vowels(message);
- }
-
- if (pseudolocalization_accents_enabled) {
- message = replace_with_accented_string(message);
- }
-
- if (pseudolocalization_fake_bidi_enabled) {
- message = wrap_with_fakebidi_characters(message);
- }
-
- StringName res = add_padding(message, length);
- return res;
-}
-
-StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- message = double_vowels(message);
- message = replace_with_accented_string(message);
- StringName res = "[!!! " + message + " !!!]";
- return res;
-}
-
-String TranslationServer::get_override_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += '*';
- }
- return res;
-}
-
-String TranslationServer::double_vowels(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += p_message[i];
- if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
- p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
- res += p_message[i];
- }
- }
- return res;
-};
-
-String TranslationServer::replace_with_accented_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- const char32_t *accented = get_accented_version(p_message[i]);
- if (accented) {
- res += accented;
- } else {
- res += p_message[i];
- }
- }
- return res;
-}
-
-String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
- String res;
- char32_t fakebidiprefix = U'\u202e';
- char32_t fakebidisuffix = U'\u202c';
- res += fakebidiprefix;
- // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
- for (int i = 0; i < p_message.length(); i++) {
- if (p_message[i] == '\n') {
- res += fakebidisuffix;
- res += p_message[i];
- res += fakebidiprefix;
- } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += fakebidisuffix;
- res += p_message[i];
- res += p_message[i + 1];
- res += fakebidiprefix;
- i++;
- } else {
- res += p_message[i];
- }
- }
- res += fakebidisuffix;
- return res;
-}
-
-String TranslationServer::add_padding(const String &p_message, int p_length) const {
- String underscores = String("_").repeat(p_length * expansion_ratio / 2);
- String prefix = pseudolocalization_prefix + underscores;
- String suffix = underscores + pseudolocalization_suffix;
-
- return prefix + p_message + suffix;
-}
-
-const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!is_ascii_char(p_character)) {
- return nullptr;
- }
-
- for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
- if (_character_to_accented[i].character == p_character) {
- return _character_to_accented[i].accented_character;
- }
- }
-
- return nullptr;
-}
-
-bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
- (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
- p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
-}
-
-#ifdef TOOLS_ENABLED
-void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
- const String pf = p_function;
- if (p_idx == 0) {
- HashMap<String, String> *target_hash_map = nullptr;
- if (pf == "get_language_name") {
- target_hash_map = &language_map;
- } else if (pf == "get_script_name") {
- target_hash_map = &script_map;
- } else if (pf == "get_country_name") {
- target_hash_map = &country_name_map;
- }
-
- if (target_hash_map) {
- for (const KeyValue<String, String> &E : *target_hash_map) {
- r_options->push_back(E.key.quote());
- }
- }
- }
- Object::get_argument_options(p_function, p_idx, r_options);
-}
-#endif // TOOLS_ENABLED
-
-void TranslationServer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
- ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
- ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
-
- ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
- ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
-
- ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
- ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
-
- ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
- ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
-
- ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
- ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
-
- ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
-
- ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
- ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
-
- ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
- ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
- ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
-
- ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
-
- ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
-
- ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
- ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
- ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
-}
-
-void TranslationServer::load_translations() {
- _load_translations("internationalization/locale/translations"); //all
- _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
-
- if (locale.substr(0, 2) != locale) {
- _load_translations("internationalization/locale/translations_" + locale);
- }
-}
-
-TranslationServer::TranslationServer() {
- singleton = this;
- init_locale_info();
-}
diff --git a/core/string/translation.h b/core/string/translation.h
index 78d6721347..2c5baae8b7 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -74,134 +74,4 @@ public:
Translation() {}
};
-class TranslationServer : public Object {
- GDCLASS(TranslationServer, Object);
-
- String locale = "en";
- String fallback;
-
- HashSet<Ref<Translation>> translations;
- Ref<Translation> tool_translation;
- Ref<Translation> property_translation;
- Ref<Translation> doc_translation;
- Ref<Translation> extractable_translation;
-
- bool enabled = true;
-
- bool pseudolocalization_enabled = false;
- bool pseudolocalization_accents_enabled = false;
- bool pseudolocalization_double_vowels_enabled = false;
- bool pseudolocalization_fake_bidi_enabled = false;
- bool pseudolocalization_override_enabled = false;
- bool pseudolocalization_skip_placeholders_enabled = false;
- bool editor_pseudolocalization = false;
- float expansion_ratio = 0.0;
- String pseudolocalization_prefix;
- String pseudolocalization_suffix;
-
- StringName tool_pseudolocalize(const StringName &p_message) const;
- String get_override_string(String &p_message) const;
- String double_vowels(String &p_message) const;
- String replace_with_accented_string(String &p_message) const;
- String wrap_with_fakebidi_characters(String &p_message) const;
- String add_padding(const String &p_message, int p_length) const;
- const char32_t *get_accented_version(char32_t p_character) const;
- bool is_placeholder(String &p_message, int p_index) const;
-
- static TranslationServer *singleton;
- bool _load_translations(const String &p_from);
- String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
-
- StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
-
- static void _bind_methods();
-
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
- struct LocaleScriptInfo {
- String name;
- String script;
- String default_country;
- HashSet<String> supported_countries;
- };
- static Vector<LocaleScriptInfo> locale_script_info;
-
- static HashMap<String, String> language_map;
- static HashMap<String, String> script_map;
- static HashMap<String, String> locale_rename_map;
- static HashMap<String, String> country_name_map;
- static HashMap<String, String> country_rename_map;
- static HashMap<String, String> variant_map;
-
- void init_locale_info();
-
-public:
- _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
-
- void set_enabled(bool p_enabled) { enabled = p_enabled; }
- _FORCE_INLINE_ bool is_enabled() const { return enabled; }
-
- void set_locale(const String &p_locale);
- String get_locale() const;
- Ref<Translation> get_translation_object(const String &p_locale);
-
- Vector<String> get_all_languages() const;
- String get_language_name(const String &p_language) const;
-
- Vector<String> get_all_scripts() const;
- String get_script_name(const String &p_script) const;
-
- Vector<String> get_all_countries() const;
- String get_country_name(const String &p_country) const;
-
- String get_locale_name(const String &p_locale) const;
-
- PackedStringArray get_loaded_locales() const;
-
- void add_translation(const Ref<Translation> &p_translation);
- void remove_translation(const Ref<Translation> &p_translation);
-
- StringName translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
-
- StringName pseudolocalize(const StringName &p_message) const;
-
- bool is_pseudolocalization_enabled() const;
- void set_pseudolocalization_enabled(bool p_enabled);
- void set_editor_pseudolocalization(bool p_enabled);
- void reload_pseudolocalization();
-
- String standardize_locale(const String &p_locale) const;
-
- int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
-
- String get_tool_locale();
- void set_tool_translation(const Ref<Translation> &p_translation);
- Ref<Translation> get_tool_translation() const;
- StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_property_translation(const Ref<Translation> &p_translation);
- StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
- void set_doc_translation(const Ref<Translation> &p_translation);
- StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_extractable_translation(const Ref<Translation> &p_translation);
- StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
-
- void setup();
-
- void clear();
-
- void load_translations();
-
-#ifdef TOOLS_ENABLED
- virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif // TOOLS_ENABLED
-
- TranslationServer();
-};
-
#endif // TRANSLATION_H
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 06fd4717d7..8e275505b0 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -140,43 +140,87 @@ int TranslationPO::_get_plural_index(int p_n) const {
input_val.clear();
input_val.push_back(p_n);
- Variant result;
- for (int i = 0; i < equi_tests.size(); i++) {
- Error err = expr->parse(equi_tests[i], input_name);
- ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text());
+ return _eq_test(equi_tests, 0);
+}
- result = expr->execute(input_val);
- ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression.");
+int TranslationPO::_eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const {
+ if (p_node.is_valid()) {
+ Error err = expr->parse(p_node->regex, input_name);
+ ERR_FAIL_COND_V_MSG(err != OK, 0, vformat("Cannot parse expression \"%s\". Error: %s", p_node->regex, expr->get_error_text()));
- // Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index.
- if (i + 1 == equi_tests.size()) {
- return result;
- }
+ Variant result = expr->execute(input_val);
+ ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, vformat("Cannot evaluate expression \"%s\".", p_node->regex));
if (bool(result)) {
- return i;
+ return _eq_test(p_node->left, result);
+ } else {
+ return _eq_test(p_node->right, result);
}
+ } else {
+ return p_result;
+ }
+}
+
+int TranslationPO::_find_unquoted(const String &p_src, char32_t p_chr) const {
+ const int len = p_src.length();
+ if (len == 0) {
+ return -1;
}
- ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug.");
+ const char32_t *src = p_src.get_data();
+ bool in_quote = false;
+ for (int i = 0; i < len; i++) {
+ if (in_quote) {
+ if (src[i] == ')') {
+ in_quote = false;
+ }
+ } else {
+ if (src[i] == '(') {
+ in_quote = true;
+ } else if (src[i] == p_chr) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
}
-void TranslationPO::_cache_plural_tests(const String &p_plural_rule) {
+void TranslationPO::_cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node) {
// Some examples of p_plural_rule passed in can have the form:
// "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic)
// "n >= 2" (French) // When evaluating the last, especially careful with this one.
// "n != 1" (English)
- int first_ques_mark = p_plural_rule.find("?");
+
+ String rule = p_plural_rule;
+ if (rule.begins_with("(") && rule.ends_with(")")) {
+ int bcount = 0;
+ for (int i = 1; i < rule.length() - 1 && bcount >= 0; i++) {
+ if (rule[i] == '(') {
+ bcount++;
+ } else if (rule[i] == ')') {
+ bcount--;
+ }
+ }
+ if (bcount == 0) {
+ rule = rule.substr(1, rule.length() - 2);
+ }
+ }
+
+ int first_ques_mark = _find_unquoted(rule, '?');
+ int first_colon = _find_unquoted(rule, ':');
+
if (first_ques_mark == -1) {
- equi_tests.push_back(p_plural_rule.strip_edges());
+ p_node->regex = rule.strip_edges();
return;
}
- String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges();
- equi_tests.push_back(equi_test);
+ p_node->regex = rule.substr(0, first_ques_mark).strip_edges();
- String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length());
- _cache_plural_tests(after_colon);
+ p_node->left.instantiate();
+ _cache_plural_tests(rule.substr(first_ques_mark + 1, first_colon - first_ques_mark - 1).strip_edges(), p_node->left);
+ p_node->right.instantiate();
+ _cache_plural_tests(rule.substr(first_colon + 1).strip_edges(), p_node->right);
}
void TranslationPO::set_plural_rule(const String &p_plural_rule) {
@@ -188,12 +232,12 @@ void TranslationPO::set_plural_rule(const String &p_plural_rule) {
int expression_start = p_plural_rule.find("=", first_semi_col) + 1;
int second_semi_col = p_plural_rule.rfind(";");
- plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start);
+ plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start).strip_edges();
// Setup the cache to make evaluating plural rule faster later on.
- plural_rule = plural_rule.replacen("(", "");
- plural_rule = plural_rule.replacen(")", "");
- _cache_plural_tests(plural_rule);
+ equi_tests.instantiate();
+ _cache_plural_tests(plural_rule, equi_tests);
+
expr.instantiate();
input_name.push_back("n");
}
diff --git a/core/string/translation_po.h b/core/string/translation_po.h
index 73f9b33a87..ba820c6ee4 100644
--- a/core/string/translation_po.h
+++ b/core/string/translation_po.h
@@ -50,7 +50,17 @@ class TranslationPO : public Translation {
String plural_rule;
// Cache temporary variables related to _get_plural_index() to make it faster
- Vector<String> equi_tests;
+ class EQNode : public RefCounted {
+ public:
+ String regex;
+ Ref<EQNode> left;
+ Ref<EQNode> right;
+ };
+ Ref<EQNode> equi_tests;
+
+ int _find_unquoted(const String &p_src, char32_t p_chr) const;
+ int _eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const;
+
Vector<String> input_name;
mutable Ref<Expression> expr;
mutable Array input_val;
@@ -59,7 +69,7 @@ class TranslationPO : public Translation {
mutable int last_plural_n = -1; // Set it to an impossible value at the beginning.
mutable int last_plural_mapped_index = 0;
- void _cache_plural_tests(const String &p_plural_rule);
+ void _cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node);
int _get_plural_index(int p_n) const;
Vector<String> _get_message_list() const override;
diff --git a/core/string/translation_server.compat.inc b/core/string/translation_server.compat.inc
new file mode 100644
index 0000000000..9d1ee8b9df
--- /dev/null
+++ b/core/string/translation_server.compat.inc
@@ -0,0 +1,38 @@
+/**************************************************************************/
+/* translation_server.compat.inc */
+/**************************************************************************/
+/* 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 DISABLE_DEPRECATED
+
+void TranslationServer::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
+ ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
+}
+
+#endif
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
new file mode 100644
index 0000000000..6e784881d0
--- /dev/null
+++ b/core/string/translation_server.cpp
@@ -0,0 +1,947 @@
+/**************************************************************************/
+/* translation_server.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 "translation_server.h"
+#include "translation_server.compat.inc"
+
+#include "core/config/project_settings.h"
+#include "core/io/resource_loader.h"
+#include "core/os/os.h"
+#include "core/string/locales.h"
+
+#ifdef TOOLS_ENABLED
+#include "main/main.h"
+#endif
+
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
+Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
+
+HashMap<String, String> TranslationServer::language_map;
+HashMap<String, String> TranslationServer::script_map;
+HashMap<String, String> TranslationServer::locale_rename_map;
+HashMap<String, String> TranslationServer::country_name_map;
+HashMap<String, String> TranslationServer::variant_map;
+HashMap<String, String> TranslationServer::country_rename_map;
+
+void TranslationServer::init_locale_info() {
+ // Init locale info.
+ language_map.clear();
+ int idx = 0;
+ while (language_list[idx][0] != nullptr) {
+ language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
+ idx++;
+ }
+
+ // Init locale-script map.
+ locale_script_info.clear();
+ idx = 0;
+ while (locale_scripts[idx][0] != nullptr) {
+ LocaleScriptInfo info;
+ info.name = locale_scripts[idx][0];
+ info.script = locale_scripts[idx][1];
+ info.default_country = locale_scripts[idx][2];
+ Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
+ for (int i = 0; i < supported_countries.size(); i++) {
+ info.supported_countries.insert(supported_countries[i]);
+ }
+ locale_script_info.push_back(info);
+ idx++;
+ }
+
+ // Init supported script list.
+ script_map.clear();
+ idx = 0;
+ while (script_list[idx][0] != nullptr) {
+ script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
+ idx++;
+ }
+
+ // Init regional variant map.
+ variant_map.clear();
+ idx = 0;
+ while (locale_variants[idx][0] != nullptr) {
+ variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
+ idx++;
+ }
+
+ // Init locale renames.
+ locale_rename_map.clear();
+ idx = 0;
+ while (locale_renames[idx][0] != nullptr) {
+ if (!String(locale_renames[idx][1]).is_empty()) {
+ locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
+ }
+ idx++;
+ }
+
+ // Init country names.
+ country_name_map.clear();
+ idx = 0;
+ while (country_names[idx][0] != nullptr) {
+ country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
+ idx++;
+ }
+
+ // Init country renames.
+ country_rename_map.clear();
+ idx = 0;
+ while (country_renames[idx][0] != nullptr) {
+ if (!String(country_renames[idx][1]).is_empty()) {
+ country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
+ }
+ idx++;
+ }
+}
+
+String TranslationServer::standardize_locale(const String &p_locale) const {
+ return _standardize_locale(p_locale, false);
+}
+
+String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
+ // 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];
+ 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];
+ }
+ 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];
+ }
+ }
+ 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();
+ }
+ }
+ 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();
+ }
+ }
+
+ // Try extract script and variant from the extra part.
+ 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";
+ break;
+ } else if (script_extra[i].to_lower() == "latin") {
+ script_name = "Latn";
+ break;
+ } else if (script_extra[i].to_lower() == "devanagari") {
+ script_name = "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();
+ }
+ }
+
+ // 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];
+ }
+
+ // Handle country renames.
+ if (country_rename_map.has(country_name)) {
+ country_name = country_rename_map[country_name];
+ }
+
+ // Remove unsupported script codes.
+ if (!script_map.has(script_name)) {
+ script_name = "";
+ }
+
+ // 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;
+ break;
+ }
+ }
+ }
+ }
+ if (!script_name.is_empty() && country_name.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;
+ 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;
+}
+
+int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ String locale_a = _standardize_locale(p_locale_a, true);
+ String locale_b = _standardize_locale(p_locale_b, true);
+
+ if (locale_a == locale_b) {
+ // Exact match.
+ 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]) {
+ // 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++;
+ }
+ }
+ }
+ return matching_elements;
+ } else {
+ // No match.
+ return 0;
+ }
+}
+
+String TranslationServer::get_locale_name(const String &p_locale) const {
+ String lang_name, script_name, country_name;
+ Vector<String> locale_elements = standardize_locale(p_locale).split("_");
+ lang_name = 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];
+ }
+ 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];
+ }
+ }
+ 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];
+ }
+ }
+
+ String name = language_map[lang_name];
+ if (!script_name.is_empty()) {
+ name = name + " (" + script_map[script_name] + ")";
+ }
+ if (!country_name.is_empty()) {
+ name = name + ", " + country_name_map[country_name];
+ }
+ return name;
+}
+
+Vector<String> TranslationServer::get_all_languages() const {
+ Vector<String> languages;
+
+ for (const KeyValue<String, String> &E : language_map) {
+ languages.push_back(E.key);
+ }
+
+ return languages;
+}
+
+String TranslationServer::get_language_name(const String &p_language) const {
+ return language_map[p_language];
+}
+
+Vector<String> TranslationServer::get_all_scripts() const {
+ Vector<String> scripts;
+
+ for (const KeyValue<String, String> &E : script_map) {
+ scripts.push_back(E.key);
+ }
+
+ return scripts;
+}
+
+String TranslationServer::get_script_name(const String &p_script) const {
+ return script_map[p_script];
+}
+
+Vector<String> TranslationServer::get_all_countries() const {
+ Vector<String> countries;
+
+ for (const KeyValue<String, String> &E : country_name_map) {
+ countries.push_back(E.key);
+ }
+
+ return countries;
+}
+
+String TranslationServer::get_country_name(const String &p_country) const {
+ return country_name_map[p_country];
+}
+
+void TranslationServer::set_locale(const String &p_locale) {
+ String new_locale = standardize_locale(p_locale);
+ if (locale == new_locale) {
+ return;
+ }
+
+ locale = new_locale;
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+String TranslationServer::get_locale() const {
+ return locale;
+}
+
+PackedStringArray TranslationServer::get_loaded_locales() const {
+ PackedStringArray locales;
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
+ String l = t->get_locale();
+
+ locales.push_back(l);
+ }
+
+ return locales;
+}
+
+void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
+ translations.insert(p_translation);
+}
+
+void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
+ translations.erase(p_translation);
+}
+
+Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
+ Ref<Translation> res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), nullptr);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ res = t;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return res;
+}
+
+void TranslationServer::clear() {
+ translations.clear();
+}
+
+StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
+ // Match given message against the translation catalog for the project locale.
+
+ if (!enabled) {
+ return p_message;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, false);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, false);
+ }
+
+ if (!res) {
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
+ }
+
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
+}
+
+StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!enabled) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
+ }
+
+ if (!res) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ return res;
+}
+
+StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
+ StringName res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), p_message);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ StringName r;
+ if (!plural) {
+ r = t->get_message(p_message, p_context);
+ } else {
+ r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ }
+ if (!r) {
+ continue;
+ }
+ res = r;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+
+ return res;
+}
+
+TranslationServer *TranslationServer::singleton = nullptr;
+
+bool TranslationServer::_load_translations(const String &p_from) {
+ if (ProjectSettings::get_singleton()->has_setting(p_from)) {
+ const Vector<String> &translation_names = GLOBAL_GET(p_from);
+
+ int tcount = translation_names.size();
+
+ if (tcount) {
+ const String *r = translation_names.ptr();
+
+ for (int i = 0; i < tcount; i++) {
+ Ref<Translation> tr = ResourceLoader::load(r[i]);
+ if (tr.is_valid()) {
+ add_translation(tr);
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void TranslationServer::setup() {
+ String test = GLOBAL_DEF("internationalization/locale/test", "");
+ test = test.strip_edges();
+ if (!test.is_empty()) {
+ set_locale(test);
+ } else {
+ set_locale(OS::get_singleton()->get_locale());
+ }
+
+ fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
+ pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
+ pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
+#ifdef TOOLS_ENABLED
+ ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
+#endif
+}
+
+void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
+ tool_translation = p_translation;
+}
+
+Ref<Translation> TranslationServer::get_tool_translation() const {
+ return tool_translation;
+}
+
+String TranslationServer::get_tool_locale() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
+ if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
+ return tool_translation->get_locale();
+ } else {
+ return "en";
+ }
+ } else {
+#else
+ {
+#endif
+ // Look for best matching loaded translation.
+ String best_locale = "en";
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), best_locale);
+ String l = t->get_locale();
+
+ int score = compare_locales(locale, l);
+ if (score > 0 && score >= best_score) {
+ best_locale = l;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return best_locale;
+ }
+}
+
+StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
+ property_translation = p_translation;
+}
+
+StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
+ if (property_translation.is_valid()) {
+ StringName r = property_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
+ doc_translation = p_translation;
+}
+
+StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
+ extractable_translation = p_translation;
+}
+
+StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+void TranslationServer::reload_pseudolocalization() {
+ pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
+ pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
+ pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
+ pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization_override_enabled) {
+ message = get_override_string(message);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ return res;
+}
+
+StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ message = double_vowels(message);
+ message = replace_with_accented_string(message);
+ StringName res = "[!!! " + message + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.length(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationServer::add_padding(const String &p_message, int p_length) const {
+ String underscores = String("_").repeat(p_length * expansion_ratio / 2);
+ String prefix = pseudolocalization_prefix + underscores;
+ String suffix = underscores + pseudolocalization_suffix;
+
+ return prefix + p_message + suffix;
+}
+
+const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
+ if (!is_ascii_alphabet_char(p_character)) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
+#ifdef TOOLS_ENABLED
+void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+ const String pf = p_function;
+ if (p_idx == 0) {
+ HashMap<String, String> *target_hash_map = nullptr;
+ if (pf == "get_language_name") {
+ target_hash_map = &language_map;
+ } else if (pf == "get_script_name") {
+ target_hash_map = &script_map;
+ } else if (pf == "get_country_name") {
+ target_hash_map = &country_name_map;
+ }
+
+ if (target_hash_map) {
+ for (const KeyValue<String, String> &E : *target_hash_map) {
+ r_options->push_back(E.key.quote());
+ }
+ }
+ }
+ Object::get_argument_options(p_function, p_idx, r_options);
+}
+#endif // TOOLS_ENABLED
+
+void TranslationServer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
+ ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+ ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
+
+ ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
+ ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
+
+ ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
+ ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
+ ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
+ ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
+
+ ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
+
+ ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
+ ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
+
+ ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
+ ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
+ ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
+
+ ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
+
+ ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
+}
+
+void TranslationServer::load_translations() {
+ _load_translations("internationalization/locale/translations"); //all
+ _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
+
+ if (locale.substr(0, 2) != locale) {
+ _load_translations("internationalization/locale/translations_" + locale);
+ }
+}
+
+TranslationServer::TranslationServer() {
+ singleton = this;
+ init_locale_info();
+}
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
new file mode 100644
index 0000000000..ebe81d9712
--- /dev/null
+++ b/core/string/translation_server.h
@@ -0,0 +1,164 @@
+/**************************************************************************/
+/* translation_server.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 TRANSLATION_SERVER_H
+#define TRANSLATION_SERVER_H
+
+#include "core/string/translation.h"
+
+class TranslationServer : public Object {
+ GDCLASS(TranslationServer, Object);
+
+ String locale = "en";
+ String fallback;
+
+ HashSet<Ref<Translation>> translations;
+ Ref<Translation> tool_translation;
+ Ref<Translation> property_translation;
+ Ref<Translation> doc_translation;
+ Ref<Translation> extractable_translation;
+
+ bool enabled = true;
+
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_enabled = false;
+ float expansion_ratio = 0.0;
+ String pseudolocalization_prefix;
+ String pseudolocalization_suffix;
+
+ StringName tool_pseudolocalize(const StringName &p_message) const;
+ String get_override_string(String &p_message) const;
+ String double_vowels(String &p_message) const;
+ String replace_with_accented_string(String &p_message) const;
+ String wrap_with_fakebidi_characters(String &p_message) const;
+ String add_padding(const String &p_message, int p_length) const;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
+ static TranslationServer *singleton;
+ bool _load_translations(const String &p_from);
+ String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
+
+ StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
+
+ static void _bind_methods();
+
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+#endif
+
+ struct LocaleScriptInfo {
+ String name;
+ String script;
+ String default_country;
+ HashSet<String> supported_countries;
+ };
+ static Vector<LocaleScriptInfo> locale_script_info;
+
+ static HashMap<String, String> language_map;
+ static HashMap<String, String> script_map;
+ static HashMap<String, String> locale_rename_map;
+ static HashMap<String, String> country_name_map;
+ static HashMap<String, String> country_rename_map;
+ static HashMap<String, String> variant_map;
+
+ void init_locale_info();
+
+public:
+ _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
+
+ void set_enabled(bool p_enabled) { enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_enabled() const { return enabled; }
+
+ void set_locale(const String &p_locale);
+ String get_locale() const;
+ Ref<Translation> get_translation_object(const String &p_locale);
+
+ Vector<String> get_all_languages() const;
+ String get_language_name(const String &p_language) const;
+
+ Vector<String> get_all_scripts() const;
+ String get_script_name(const String &p_script) const;
+
+ Vector<String> get_all_countries() const;
+ String get_country_name(const String &p_country) const;
+
+ String get_locale_name(const String &p_locale) const;
+
+ PackedStringArray get_loaded_locales() const;
+
+ void add_translation(const Ref<Translation> &p_translation);
+ void remove_translation(const Ref<Translation> &p_translation);
+
+ StringName translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ StringName pseudolocalize(const StringName &p_message) const;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void reload_pseudolocalization();
+
+ String standardize_locale(const String &p_locale) const;
+
+ int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
+
+ String get_tool_locale();
+ void set_tool_translation(const Ref<Translation> &p_translation);
+ Ref<Translation> get_tool_translation() const;
+ StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_property_translation(const Ref<Translation> &p_translation);
+ StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
+ void set_doc_translation(const Ref<Translation> &p_translation);
+ StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_extractable_translation(const Ref<Translation> &p_translation);
+ StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ void setup();
+
+ void clear();
+
+ void load_translations();
+
+#ifdef TOOLS_ENABLED
+ virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+#endif // TOOLS_ENABLED
+
+ TranslationServer();
+};
+
+#endif // TRANSLATION_SERVER_H
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 2b62b72a51..5a1f6925aa 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -36,7 +36,7 @@
#include "core/os/memory.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
#include "core/version_generated.gen.h"
@@ -1184,6 +1184,26 @@ int String::get_slice_count(const String &p_splitter) const {
return slices;
}
+int String::get_slice_count(const char *p_splitter) const {
+ if (is_empty()) {
+ return 0;
+ }
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ return 0;
+ }
+
+ int pos = 0;
+ int slices = 1;
+ int splitter_length = strlen(p_splitter);
+
+ while ((pos = find(p_splitter, pos)) >= 0) {
+ slices++;
+ pos += splitter_length;
+ }
+
+ return slices;
+}
+
String String::get_slice(const String &p_splitter, int p_slice) const {
if (is_empty() || p_splitter.is_empty()) {
return "";
@@ -1224,6 +1244,47 @@ String String::get_slice(const String &p_splitter, int p_slice) const {
return ""; //no find!
}
+String String::get_slice(const char *p_splitter, int p_slice) const {
+ if (is_empty() || p_splitter == nullptr || *p_splitter == '\0') {
+ return "";
+ }
+
+ int pos = 0;
+ int prev_pos = 0;
+ //int slices=1;
+ if (p_slice < 0) {
+ return "";
+ }
+ if (find(p_splitter) == -1) {
+ return *this;
+ }
+
+ int i = 0;
+ int splitter_length = strlen(p_splitter);
+ while (true) {
+ pos = find(p_splitter, pos);
+ if (pos == -1) {
+ pos = length(); //reached end
+ }
+
+ int from = prev_pos;
+ //int to=pos;
+
+ if (p_slice == i) {
+ return substr(from, pos - from);
+ }
+
+ if (pos == length()) { //reached end and no find
+ break;
+ }
+ pos += splitter_length;
+ prev_pos = pos;
+ i++;
+ }
+
+ return ""; //no find!
+}
+
String String::get_slicec(char32_t p_splitter, int p_slice) const {
if (is_empty()) {
return String();
@@ -1338,6 +1399,54 @@ Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p
return ret;
}
+Vector<String> String::split(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+
+ if (is_empty()) {
+ if (p_allow_empty) {
+ ret.push_back("");
+ }
+ return ret;
+ }
+
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int end;
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ end = from + 1;
+ } else {
+ end = find(p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ }
+ if (p_allow_empty || (end > from)) {
+ if (p_maxsplit <= 0) {
+ ret.push_back(substr(from, end - from));
+ } else {
+ // Put rest of the string and leave cycle.
+ if (p_maxsplit == ret.size()) {
+ ret.push_back(substr(from, len));
+ break;
+ }
+
+ // Otherwise, push items until positive limit is reached.
+ ret.push_back(substr(from, end - from));
+ }
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + strlen(p_splitter);
+ }
+
+ return ret;
+}
+
Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> ret;
const int len = length();
@@ -1380,18 +1489,64 @@ Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int
return ret;
}
+Vector<String> String::rsplit(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+ const int len = length();
+ const int splitter_length = strlen(p_splitter);
+ int remaining_len = len;
+
+ while (true) {
+ if (remaining_len < splitter_length || (p_maxsplit > 0 && p_maxsplit == ret.size())) {
+ // no room for another splitter or hit max splits, push what's left and we're done
+ if (p_allow_empty || remaining_len > 0) {
+ ret.push_back(substr(0, remaining_len));
+ }
+ break;
+ }
+
+ int left_edge;
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ left_edge = remaining_len - 1;
+ if (left_edge == 0) {
+ left_edge--; // Skip to the < 0 condition.
+ }
+ } else {
+ left_edge = rfind(p_splitter, remaining_len - splitter_length);
+ }
+
+ if (left_edge < 0) {
+ // no more splitters, we're done
+ ret.push_back(substr(0, remaining_len));
+ break;
+ }
+
+ int substr_start = left_edge + splitter_length;
+ if (p_allow_empty || substr_start < remaining_len) {
+ ret.push_back(substr(substr_start, remaining_len - substr_start));
+ }
+
+ remaining_len = left_edge;
+ }
+
+ ret.reverse();
+ return ret;
+}
+
Vector<double> String::split_floats(const String &p_splitter, bool p_allow_empty) const {
Vector<double> ret;
int from = 0;
int len = length();
+ String buffer = *this;
while (true) {
int end = find(p_splitter, from);
if (end < 0) {
end = len;
}
if (p_allow_empty || (end > from)) {
- ret.push_back(String::to_float(&get_data()[from]));
+ buffer[end] = 0;
+ ret.push_back(String::to_float(&buffer.get_data()[from]));
+ buffer[end] = _cowdata.get(end);
}
if (end == len) {
@@ -1409,6 +1564,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
int from = 0;
int len = length();
+ String buffer = *this;
while (true) {
int idx;
int end = findmk(p_splitters, from, &idx);
@@ -1420,7 +1576,9 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
}
if (p_allow_empty || (end > from)) {
- ret.push_back(String::to_float(&get_data()[from]));
+ buffer[end] = 0;
+ ret.push_back(String::to_float(&buffer.get_data()[from]));
+ buffer[end] = _cowdata.get(end);
}
if (end == len) {
@@ -1487,13 +1645,43 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo
}
String String::join(const Vector<String> &parts) const {
+ if (parts.is_empty()) {
+ return String();
+ } else if (parts.size() == 1) {
+ return parts[0];
+ }
+
+ const int this_length = length();
+
+ int new_size = (parts.size() - 1) * this_length;
+ for (const String &part : parts) {
+ new_size += part.length();
+ }
+ new_size += 1;
+
String ret;
- for (int i = 0; i < parts.size(); ++i) {
- if (i > 0) {
- ret += *this;
+ ret.resize(new_size);
+ char32_t *ret_ptrw = ret.ptrw();
+ const char32_t *this_ptr = ptr();
+
+ bool first = true;
+ for (const String &part : parts) {
+ if (first) {
+ first = false;
+ } else if (this_length) {
+ memcpy(ret_ptrw, this_ptr, this_length * sizeof(char32_t));
+ ret_ptrw += this_length;
+ }
+
+ const int part_length = part.length();
+ if (part_length) {
+ memcpy(ret_ptrw, part.ptr(), part_length * sizeof(char32_t));
+ ret_ptrw += part_length;
}
- ret += parts[i];
}
+
+ *ret_ptrw = 0;
+
return ret;
}
@@ -2997,7 +3185,7 @@ Vector<uint8_t> String::sha256_buffer() const {
}
String String::insert(int p_at_pos, const String &p_string) const {
- if (p_at_pos < 0) {
+ if (p_string.is_empty() || p_at_pos < 0) {
return *this;
}
@@ -3005,17 +3193,27 @@ String String::insert(int p_at_pos, const String &p_string) const {
p_at_pos = length();
}
- String pre;
+ String ret;
+ ret.resize(length() + p_string.length() + 1);
+ char32_t *ret_ptrw = ret.ptrw();
+ const char32_t *this_ptr = ptr();
+
if (p_at_pos > 0) {
- pre = substr(0, p_at_pos);
+ memcpy(ret_ptrw, this_ptr, p_at_pos * sizeof(char32_t));
+ ret_ptrw += p_at_pos;
}
- String post;
+ memcpy(ret_ptrw, p_string.ptr(), p_string.length() * sizeof(char32_t));
+ ret_ptrw += p_string.length();
+
if (p_at_pos < length()) {
- post = substr(p_at_pos, length() - p_at_pos);
+ memcpy(ret_ptrw, this_ptr + p_at_pos, (length() - p_at_pos) * sizeof(char32_t));
+ ret_ptrw += length() - p_at_pos;
}
- return pre + p_string + post;
+ *ret_ptrw = 0;
+
+ return ret;
}
String String::erase(int p_pos, int p_chars) const {
@@ -3087,23 +3285,20 @@ int String::find(const String &p_str, int p_from) const {
}
int String::find(const char *p_str, int p_from) const {
- if (p_from < 0) {
+ if (p_from < 0 || !p_str) {
return -1;
}
+ const int src_len = strlen(p_str);
+
const int len = length();
- if (len == 0) {
+ if (len == 0 || src_len == 0) {
return -1; // won't find anything!
}
const char32_t *src = get_data();
- int src_len = 0;
- while (p_str[src_len] != '\0') {
- src_len++;
- }
-
if (src_len == 1) {
const char32_t needle = p_str[0];
@@ -3238,6 +3433,46 @@ int String::findn(const String &p_str, int p_from) const {
return -1;
}
+int String::findn(const char *p_str, int p_from) const {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ int src_len = strlen(p_str);
+
+ if (src_len == 0 || length() == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *srcd = get_data();
+
+ for (int i = p_from; i <= (length() - src_len); i++) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= length()) {
+ ERR_PRINT("read_pos>=length()");
+ return -1;
+ }
+
+ char32_t src = _find_lower(srcd[read_pos]);
+ char32_t dst = _find_lower(p_str[j]);
+
+ if (src != dst) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
int String::rfind(const String &p_str, int p_from) const {
// establish a limit
int limit = length() - p_str.length();
@@ -3285,6 +3520,57 @@ int String::rfind(const String &p_str, int p_from) const {
return -1;
}
+int String::rfind(const char *p_str, int p_from) const {
+ const int source_length = length();
+ int substring_length = strlen(p_str);
+
+ if (source_length == 0 || substring_length == 0) {
+ return -1; // won't find anything!
+ }
+
+ // establish a limit
+ int limit = length() - substring_length;
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ int starting_point;
+ if (p_from < 0) {
+ starting_point = limit;
+ } else if (p_from > limit) {
+ starting_point = limit;
+ } else {
+ starting_point = p_from;
+ }
+
+ const char32_t *source = get_data();
+
+ for (int i = starting_point; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < substring_length; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= source_length) {
+ ERR_PRINT("read_pos>=source_length");
+ return -1;
+ }
+
+ const char32_t key_needle = p_str[j];
+ if (source[read_pos] != key_needle) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
int String::rfindn(const String &p_str, int p_from) const {
// establish a limit
int limit = length() - p_str.length();
@@ -3335,6 +3621,60 @@ int String::rfindn(const String &p_str, int p_from) const {
return -1;
}
+int String::rfindn(const char *p_str, int p_from) const {
+ const int source_length = length();
+ int substring_length = strlen(p_str);
+
+ if (source_length == 0 || substring_length == 0) {
+ return -1; // won't find anything!
+ }
+
+ // establish a limit
+ int limit = length() - substring_length;
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ int starting_point;
+ if (p_from < 0) {
+ starting_point = limit;
+ } else if (p_from > limit) {
+ starting_point = limit;
+ } else {
+ starting_point = p_from;
+ }
+
+ const char32_t *source = get_data();
+
+ for (int i = starting_point; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < substring_length; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= source_length) {
+ ERR_PRINT("read_pos>=source_length");
+ return -1;
+ }
+
+ const char32_t key_needle = p_str[j];
+ int srcc = _find_lower(source[read_pos]);
+ int keyc = _find_lower(key_needle);
+
+ if (srcc != keyc) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
bool String::ends_with(const String &p_string) const {
int l = p_string.length();
if (l > length()) {
@@ -3357,6 +3697,31 @@ bool String::ends_with(const String &p_string) const {
return true;
}
+bool String::ends_with(const char *p_string) const {
+ if (!p_string) {
+ return false;
+ }
+
+ int l = strlen(p_string);
+ if (l > length()) {
+ return false;
+ }
+
+ if (l == 0) {
+ return true;
+ }
+
+ const char32_t *s = &operator[](length() - l);
+
+ for (int i = 0; i < l; i++) {
+ if (static_cast<char32_t>(p_string[i]) != s[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool String::begins_with(const String &p_string) const {
int l = p_string.length();
if (l > length()) {
@@ -3380,11 +3745,11 @@ bool String::begins_with(const String &p_string) const {
}
bool String::begins_with(const char *p_string) const {
- int l = length();
if (!p_string) {
return false;
}
+ int l = length();
if (l == 0) {
return *p_string == 0;
}
@@ -3456,14 +3821,61 @@ int String::_count(const String &p_string, int p_from, int p_to, bool p_case_ins
return c;
}
+int String::_count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const {
+ int substring_length = strlen(p_string);
+ if (substring_length == 0) {
+ return 0;
+ }
+ const int source_length = length();
+
+ if (source_length < substring_length) {
+ return 0;
+ }
+ String str;
+ int search_limit = p_to;
+ if (p_from >= 0 && p_to >= 0) {
+ if (p_to == 0) {
+ search_limit = source_length;
+ } else if (p_from >= p_to) {
+ return 0;
+ }
+ if (p_from == 0 && search_limit == source_length) {
+ str = String();
+ str.copy_from_unchecked(&get_data()[0], source_length);
+ } else {
+ str = substr(p_from, search_limit - p_from);
+ }
+ } else {
+ return 0;
+ }
+ int c = 0;
+ int idx = -1;
+ do {
+ idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string);
+ if (idx != -1) {
+ str = str.substr(idx + substring_length, str.length() - substring_length);
+ ++c;
+ }
+ } while (idx != -1);
+ return c;
+}
+
int String::count(const String &p_string, int p_from, int p_to) const {
return _count(p_string, p_from, p_to, false);
}
+int String::count(const char *p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, false);
+}
+
int String::countn(const String &p_string, int p_from, int p_to) const {
return _count(p_string, p_from, p_to, true);
}
+int String::countn(const char *p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, true);
+}
+
bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const {
int len = length();
if (len == 0) {
@@ -3598,7 +4010,7 @@ String String::format(const Variant &values, const String &placeholder) const {
Variant v_val = values_arr[i];
String val = v_val;
- if (placeholder.find("_") > -1) {
+ if (placeholder.contains("_")) {
new_string = new_string.replace(placeholder.replace("_", i_as_str), val);
} else {
new_string = new_string.replace_first(placeholder, val);
@@ -3673,6 +4085,16 @@ String String::replace_first(const String &p_key, const String &p_with) const {
return *this;
}
+String String::replace_first(const char *p_key, const char *p_with) const {
+ int pos = find(p_key);
+ if (pos >= 0) {
+ int substring_length = strlen(p_key);
+ return substr(0, pos) + p_with + substr(pos + substring_length, length());
+ }
+
+ return *this;
+}
+
String String::replacen(const String &p_key, const String &p_with) const {
String new_string;
int search_from = 0;
@@ -3692,6 +4114,31 @@ String String::replacen(const String &p_key, const String &p_with) const {
return new_string;
}
+String String::replacen(const char *p_key, const char *p_with) const {
+ String new_string;
+ int search_from = 0;
+ int result = 0;
+ int substring_length = strlen(p_key);
+
+ if (substring_length == 0) {
+ return *this; // there's nothing to match or substitute
+ }
+
+ while ((result = findn(p_key, search_from)) >= 0) {
+ new_string += substr(search_from, result - search_from);
+ new_string += p_with;
+ search_from = result + substring_length;
+ }
+
+ if (search_from == 0) {
+ return *this;
+ }
+
+ new_string += substr(search_from, length() - search_from);
+
+ return new_string;
+}
+
String String::repeat(int p_count) const {
ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number.");
@@ -4420,6 +4867,15 @@ String String::trim_prefix(const String &p_prefix) const {
return s;
}
+String String::trim_prefix(const char *p_prefix) const {
+ String s = *this;
+ if (s.begins_with(p_prefix)) {
+ int prefix_length = strlen(p_prefix);
+ return s.substr(prefix_length, s.length() - prefix_length);
+ }
+ return s;
+}
+
String String::trim_suffix(const String &p_suffix) const {
String s = *this;
if (s.ends_with(p_suffix)) {
@@ -4428,6 +4884,14 @@ String String::trim_suffix(const String &p_suffix) const {
return s;
}
+String String::trim_suffix(const char *p_suffix) const {
+ String s = *this;
+ if (s.ends_with(p_suffix)) {
+ return s.substr(0, s.length() - strlen(p_suffix));
+ }
+ return s;
+}
+
bool String::is_valid_int() const {
int len = length();
@@ -4903,6 +5367,11 @@ String String::lpad(int min_length, const String &character) const {
// "fish %s %d pie" % ["frog", 12]
// In case of an error, the string returned is the error description and "error" is true.
String String::sprintf(const Array &values, bool *error) const {
+ static const String ZERO("0");
+ static const String SPACE(" ");
+ static const String MINUS("-");
+ static const String PLUS("+");
+
String formatted;
char32_t *self = (char32_t *)get_data();
bool in_format = false;
@@ -4925,7 +5394,7 @@ String String::sprintf(const Array &values, bool *error) const {
if (in_format) { // We have % - let's see what else we get.
switch (c) {
case '%': { // Replace %% with %
- formatted += chr(c);
+ formatted += c;
in_format = false;
break;
}
@@ -4975,7 +5444,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding.
int pad_chars_count = (negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeros ? String("0") : String(" ");
+ const String &pad_char = pad_with_zeros ? ZERO : SPACE;
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4984,7 +5453,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Sign.
if (show_sign || negative) {
- String sign_char = negative ? "-" : "+";
+ const String &sign_char = negative ? MINUS : PLUS;
if (left_justified) {
str = str.insert(0, sign_char);
} else {
@@ -5021,7 +5490,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+ const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -5030,7 +5499,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Add sign if needed.
if (show_sign || is_negative) {
- String sign_char = is_negative ? "-" : "+";
+ const String &sign_char = is_negative ? MINUS : PLUS;
if (left_justified) {
str = str.insert(0, sign_char);
} else {
@@ -5083,7 +5552,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding. Leave room for sign later if required.
int pad_chars_count = val < 0 ? min_chars - 1 : min_chars;
- String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+ const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros
if (left_justified) {
number_str = number_str.rpad(pad_chars_count, pad_char);
} else {
@@ -5093,9 +5562,9 @@ String String::sprintf(const Array &values, bool *error) const {
// Add sign if needed.
if (val < 0) {
if (left_justified) {
- number_str = number_str.insert(0, "-");
+ number_str = number_str.insert(0, MINUS);
} else {
- number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, "-");
+ number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, MINUS);
}
}
@@ -5260,7 +5729,7 @@ String String::sprintf(const Array &values, bool *error) const {
in_decimals = false;
break;
default:
- formatted += chr(c);
+ formatted += c;
}
}
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 693df6dcba..9df2d56e80 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -198,6 +198,7 @@ class String {
bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;
int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const;
+ int _count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const;
String _camelcase_to_underscore() const;
public:
@@ -288,14 +289,18 @@ public:
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 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 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
bool match(const String &p_wildcard) const;
bool matchn(const String &p_wildcard) const;
bool begins_with(const String &p_string) const;
bool begins_with(const char *p_string) const;
bool ends_with(const String &p_string) const;
+ bool ends_with(const char *p_string) const;
bool is_enclosed_in(const String &p_string) const;
bool is_subsequence_of(const String &p_string) const;
bool is_subsequence_ofn(const String &p_string) const;
@@ -304,9 +309,11 @@ public:
float similarity(const String &p_string) const;
String format(const Variant &values, const String &placeholder = "{_}") const;
String replace_first(const String &p_key, const String &p_with) const;
+ String replace_first(const char *p_key, const char *p_with) const;
String replace(const String &p_key, const String &p_with) const;
String replace(const char *p_key, const char *p_with) const;
String replacen(const String &p_key, const String &p_with) const;
+ String replacen(const char *p_key, const char *p_with) const;
String repeat(int p_count) const;
String reverse() const;
String insert(int p_at_pos, const String &p_string) const;
@@ -314,7 +321,9 @@ public:
String pad_decimals(int p_digits) const;
String pad_zeros(int p_digits) const;
String trim_prefix(const String &p_prefix) const;
+ String trim_prefix(const char *p_prefix) const;
String trim_suffix(const String &p_suffix) const;
+ String trim_suffix(const char *p_suffix) const;
String lpad(int min_length, const String &character = " ") const;
String rpad(int min_length, const String &character = " ") const;
String sprintf(const Array &values, bool *error) const;
@@ -353,11 +362,15 @@ public:
String get_with_code_lines() const;
int get_slice_count(const String &p_splitter) const;
+ int get_slice_count(const char *p_splitter) const;
String get_slice(const String &p_splitter, int p_slice) const;
+ String get_slice(const char *p_splitter, int p_slice) const;
String get_slicec(char32_t p_splitter, int p_slice) const;
Vector<String> split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
+ Vector<String> split(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> rsplit(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
+ Vector<String> rsplit(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> split_spaces() const;
Vector<double> split_floats(const String &p_splitter, bool p_allow_empty = true) const;
Vector<float> split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
@@ -372,7 +385,9 @@ public:
String to_lower() const;
int count(const String &p_string, int p_from = 0, int p_to = 0) const;
+ int count(const char *p_string, int p_from = 0, int p_to = 0) const;
int countn(const String &p_string, int p_from = 0, int p_to = 0) const;
+ int countn(const char *p_string, int p_from = 0, int p_to = 0) const;
String left(int p_len) const;
String right(int p_len) const;
@@ -414,6 +429,8 @@ public:
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; }
_FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; }
+ _FORCE_INLINE_ bool containsn(const char *p_str) const { return findn(p_str) != -1; }
+ _FORCE_INLINE_ bool containsn(const String &p_str) const { return findn(p_str) != -1; }
// path functions
bool is_absolute_path() const;
diff --git a/core/templates/command_queue_mt.cpp b/core/templates/command_queue_mt.cpp
index 6ecd75ebc1..ef75a70868 100644
--- a/core/templates/command_queue_mt.cpp
+++ b/core/templates/command_queue_mt.cpp
@@ -41,43 +41,9 @@ void CommandQueueMT::unlock() {
mutex.unlock();
}
-void CommandQueueMT::wait_for_flush() {
- // wait one millisecond for a flush to happen
- OS::get_singleton()->delay_usec(1000);
-}
-
-CommandQueueMT::SyncSemaphore *CommandQueueMT::_alloc_sync_sem() {
- int idx = -1;
-
- while (true) {
- lock();
- for (int i = 0; i < SYNC_SEMAPHORES; i++) {
- if (!sync_sems[i].in_use) {
- sync_sems[i].in_use = true;
- idx = i;
- break;
- }
- }
- unlock();
-
- if (idx == -1) {
- wait_for_flush();
- } else {
- break;
- }
- }
-
- return &sync_sems[idx];
-}
-
-CommandQueueMT::CommandQueueMT(bool p_sync) {
- if (p_sync) {
- sync = memnew(Semaphore);
- }
+CommandQueueMT::CommandQueueMT() {
+ command_mem.reserve(DEFAULT_COMMAND_MEM_SIZE_KB * 1024);
}
CommandQueueMT::~CommandQueueMT() {
- if (sync) {
- memdelete(sync);
- }
}
diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h
index e26f11d28a..1e6c6e42a9 100644
--- a/core/templates/command_queue_mt.h
+++ b/core/templates/command_queue_mt.h
@@ -32,9 +32,9 @@
#define COMMAND_QUEUE_MT_H
#include "core/object/worker_thread_pool.h"
+#include "core/os/condition_variable.h"
#include "core/os/memory.h"
#include "core/os/mutex.h"
-#include "core/os/semaphore.h"
#include "core/string/print_string.h"
#include "core/templates/local_vector.h"
#include "core/templates/simple_type.h"
@@ -208,7 +208,7 @@
#define ARG(N) p##N
#define PARAM(N) P##N p##N
#define TYPE_PARAM(N) typename P##N
-#define PARAM_DECL(N) typename GetSimpleTypeT<P##N>::type_t p##N
+#define PARAM_DECL(N) GetSimpleTypeT<P##N> p##N
#define DECL_CMD(N) \
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
@@ -248,16 +248,17 @@
#define CMD_TYPE(N) Command##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
#define CMD_ASSIGN_PARAM(N) cmd->p##N = p##N
-#define DECL_PUSH(N) \
- template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
- void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
- CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
- cmd->instance = p_instance; \
- cmd->method = p_method; \
- SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
- unlock(); \
- if (sync) \
- sync->post(); \
+#define DECL_PUSH(N) \
+ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
+ void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
+ MutexLock mlock(mutex); \
+ CMD_TYPE(N) *cmd = allocate<CMD_TYPE(N)>(); \
+ cmd->instance = p_instance; \
+ cmd->method = p_method; \
+ SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
+ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
+ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
+ } \
}
#define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R>
@@ -265,18 +266,17 @@
#define DECL_PUSH_AND_RET(N) \
template <typename T, typename M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) typename R> \
void push_and_ret(T *p_instance, M p_method, COMMA_SEP_LIST(PARAM, N) COMMA(N) R *r_ret) { \
- SyncSemaphore *ss = _alloc_sync_sem(); \
- CMD_RET_TYPE(N) *cmd = allocate_and_lock<CMD_RET_TYPE(N)>(); \
+ MutexLock mlock(mutex); \
+ CMD_RET_TYPE(N) *cmd = allocate<CMD_RET_TYPE(N)>(); \
cmd->instance = p_instance; \
cmd->method = p_method; \
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
cmd->ret = r_ret; \
- cmd->sync_sem = ss; \
- unlock(); \
- if (sync) \
- sync->post(); \
- ss->sem.wait(); \
- ss->in_use = false; \
+ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
+ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
+ } \
+ sync_tail++; \
+ _wait_for_sync(mlock); \
}
#define CMD_SYNC_TYPE(N) CommandSync##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
@@ -284,38 +284,31 @@
#define DECL_PUSH_AND_SYNC(N) \
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
void push_and_sync(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
- SyncSemaphore *ss = _alloc_sync_sem(); \
- CMD_SYNC_TYPE(N) *cmd = allocate_and_lock<CMD_SYNC_TYPE(N)>(); \
+ MutexLock mlock(mutex); \
+ CMD_SYNC_TYPE(N) *cmd = allocate<CMD_SYNC_TYPE(N)>(); \
cmd->instance = p_instance; \
cmd->method = p_method; \
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
- cmd->sync_sem = ss; \
- unlock(); \
- if (sync) \
- sync->post(); \
- ss->sem.wait(); \
- ss->in_use = false; \
+ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
+ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
+ } \
+ sync_tail++; \
+ _wait_for_sync(mlock); \
}
#define MAX_CMD_PARAMS 15
class CommandQueueMT {
- struct SyncSemaphore {
- Semaphore sem;
- bool in_use = false;
- };
-
struct CommandBase {
+ bool sync = false;
virtual void call() = 0;
- virtual SyncSemaphore *get_sync_semaphore() { return nullptr; }
- virtual ~CommandBase() = default; // Won't be called.
+ virtual ~CommandBase() = default;
};
struct SyncCommand : public CommandBase {
- SyncSemaphore *sync_sem = nullptr;
-
- virtual SyncSemaphore *get_sync_semaphore() override {
- return sync_sem;
+ virtual void call() override {}
+ SyncCommand() {
+ sync = true;
}
};
@@ -332,15 +325,15 @@ class CommandQueueMT {
/***** BASE *******/
- enum {
- DEFAULT_COMMAND_MEM_SIZE_KB = 256,
- SYNC_SEMAPHORES = 8
- };
+ static const uint32_t DEFAULT_COMMAND_MEM_SIZE_KB = 64;
+ BinaryMutex mutex;
LocalVector<uint8_t> command_mem;
- SyncSemaphore sync_sems[SYNC_SEMAPHORES];
- Mutex mutex;
- Semaphore *sync = nullptr;
+ ConditionVariable sync_cond_var;
+ uint32_t sync_head = 0;
+ uint32_t sync_tail = 0;
+ uint32_t sync_awaiters = 0;
+ WorkerThreadPool::TaskID pump_task_id = WorkerThreadPool::INVALID_TASK_ID;
uint64_t flush_read_ptr = 0;
template <typename T>
@@ -354,45 +347,67 @@ class CommandQueueMT {
return cmd;
}
- template <typename T>
- T *allocate_and_lock() {
- lock();
- T *ret = allocate<T>();
- return ret;
+ _FORCE_INLINE_ void _prevent_sync_wraparound() {
+ bool safe_to_reset = !sync_awaiters;
+ bool already_sync_to_latest = sync_head == sync_tail;
+ if (safe_to_reset && already_sync_to_latest) {
+ sync_head = 0;
+ sync_tail = 0;
+ }
}
void _flush() {
- lock();
-
if (unlikely(flush_read_ptr)) {
// Re-entrant call.
- unlock();
return;
}
- WorkerThreadPool::thread_enter_command_queue_mt_flush(this);
+ lock();
+
+ uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&mutex);
while (flush_read_ptr < command_mem.size()) {
uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr];
flush_read_ptr += 8;
CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
-
- SyncSemaphore *sync_sem = cmd->get_sync_semaphore();
cmd->call();
- if (sync_sem) {
- sync_sem->sem.post(); // Release in case it needs sync/ret.
+
+ // Handle potential realloc due to the command and unlock allowance.
+ cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
+
+ if (unlikely(cmd->sync)) {
+ sync_head++;
+ unlock(); // Give an opportunity to awaiters right away.
+ sync_cond_var.notify_all();
+ lock();
+ // Handle potential realloc happened during unlock.
+ cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
}
+ cmd->~CommandBase();
+
flush_read_ptr += size;
}
- WorkerThreadPool::thread_exit_command_queue_mt_flush();
+ WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
command_mem.clear();
flush_read_ptr = 0;
+
+ _prevent_sync_wraparound();
+
unlock();
}
- void wait_for_flush();
- SyncSemaphore *_alloc_sync_sem();
+ _FORCE_INLINE_ void _wait_for_sync(MutexLock<BinaryMutex> &p_lock) {
+ sync_awaiters++;
+ uint32_t sync_head_goal = sync_tail;
+ do {
+ sync_cond_var.wait(p_lock);
+ } while (sync_head < sync_head_goal);
+ sync_awaiters--;
+ _prevent_sync_wraparound();
+ }
+
+ void _no_op() {}
public:
void lock();
@@ -415,17 +430,28 @@ public:
_flush();
}
}
+
void flush_all() {
_flush();
}
+ void sync() {
+ push_and_sync(this, &CommandQueueMT::_no_op);
+ }
+
void wait_and_flush() {
- ERR_FAIL_NULL(sync);
- sync->wait();
+ ERR_FAIL_COND(pump_task_id == WorkerThreadPool::INVALID_TASK_ID);
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(pump_task_id);
_flush();
}
- CommandQueueMT(bool p_sync);
+ void set_pump_task_id(WorkerThreadPool::TaskID p_task_id) {
+ lock();
+ pump_task_id = p_task_id;
+ unlock();
+ }
+
+ CommandQueueMT();
~CommandQueueMT();
};
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index f22ae1f1d3..fedcfaec3b 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -160,7 +160,7 @@ private:
return *out;
}
- void _unref(void *p_data);
+ void _unref();
void _ref(const CowData *p_from);
void _ref(const CowData &p_from);
USize _copy_on_write();
@@ -222,12 +222,15 @@ public:
}
Error insert(Size p_pos, const T &p_val) {
- ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER);
- resize(size() + 1);
- for (Size i = (size() - 1); i > p_pos; i--) {
- set(i, get(i - 1));
+ Size new_size = size() + 1;
+ ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER);
+ Error err = resize(new_size);
+ ERR_FAIL_COND_V(err, err);
+ T *p = ptrw();
+ for (Size i = new_size - 1; i > p_pos; i--) {
+ p[i] = p[i - 1];
}
- set(p_pos, p_val);
+ p[p_pos] = p_val;
return OK;
}
@@ -242,30 +245,29 @@ public:
};
template <typename T>
-void CowData<T>::_unref(void *p_data) {
- if (!p_data) {
+void CowData<T>::_unref() {
+ if (!_ptr) {
return;
}
SafeNumeric<USize> *refc = _get_refcount();
-
if (refc->decrement() > 0) {
return; // still in use
}
// clean up
if constexpr (!std::is_trivially_destructible_v<T>) {
- USize *count = _get_size();
- T *data = (T *)(count + 1);
+ USize current_size = *_get_size();
- for (USize i = 0; i < *count; ++i) {
+ for (USize i = 0; i < current_size; ++i) {
// call destructors
- data[i].~T();
+ T *t = &_ptr[i];
+ t->~T();
}
}
// free mem
- Memory::free_static(((uint8_t *)p_data) - DATA_OFFSET, false);
+ Memory::free_static(((uint8_t *)_ptr) - DATA_OFFSET, false);
}
template <typename T>
@@ -300,7 +302,7 @@ typename CowData<T>::USize CowData<T>::_copy_on_write() {
}
}
- _unref(_ptr);
+ _unref();
_ptr = _data_ptr;
rc = 1;
@@ -321,7 +323,7 @@ Error CowData<T>::resize(Size p_size) {
if (p_size == 0) {
// wants to clean up
- _unref(_ptr);
+ _unref();
_ptr = nullptr;
return OK;
}
@@ -460,7 +462,7 @@ void CowData<T>::_ref(const CowData &p_from) {
return; // self assign, do nothing.
}
- _unref(_ptr);
+ _unref();
_ptr = nullptr;
if (!p_from._ptr) {
@@ -474,7 +476,7 @@ void CowData<T>::_ref(const CowData &p_from) {
template <typename T>
CowData<T>::~CowData() {
- _unref(_ptr);
+ _unref();
}
#if defined(__GNUC__) && !defined(__clang__)
diff --git a/core/templates/list.h b/core/templates/list.h
index b4d4beb930..6663f06c30 100644
--- a/core/templates/list.h
+++ b/core/templates/list.h
@@ -139,54 +139,58 @@ public:
typedef T ValueType;
- struct Iterator {
- _FORCE_INLINE_ T &operator*() const {
+ struct ConstIterator {
+ _FORCE_INLINE_ const T &operator*() const {
return E->get();
}
- _FORCE_INLINE_ T *operator->() const { return &E->get(); }
- _FORCE_INLINE_ Iterator &operator++() {
+ _FORCE_INLINE_ const T *operator->() const { return &E->get(); }
+ _FORCE_INLINE_ ConstIterator &operator++() {
E = E->next();
return *this;
}
- _FORCE_INLINE_ Iterator &operator--() {
+ _FORCE_INLINE_ ConstIterator &operator--() {
E = E->prev();
return *this;
}
- _FORCE_INLINE_ bool operator==(const Iterator &b) const { return E == b.E; }
- _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return E != b.E; }
+ _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return E == b.E; }
+ _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return E != b.E; }
- Iterator(Element *p_E) { E = p_E; }
- Iterator() {}
- Iterator(const Iterator &p_it) { E = p_it.E; }
+ _FORCE_INLINE_ ConstIterator(const Element *p_E) { E = p_E; }
+ _FORCE_INLINE_ ConstIterator() {}
+ _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) { E = p_it.E; }
private:
- Element *E = nullptr;
+ const Element *E = nullptr;
};
- struct ConstIterator {
- _FORCE_INLINE_ const T &operator*() const {
+ struct Iterator {
+ _FORCE_INLINE_ T &operator*() const {
return E->get();
}
- _FORCE_INLINE_ const T *operator->() const { return &E->get(); }
- _FORCE_INLINE_ ConstIterator &operator++() {
+ _FORCE_INLINE_ T *operator->() const { return &E->get(); }
+ _FORCE_INLINE_ Iterator &operator++() {
E = E->next();
return *this;
}
- _FORCE_INLINE_ ConstIterator &operator--() {
+ _FORCE_INLINE_ Iterator &operator--() {
E = E->prev();
return *this;
}
- _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return E == b.E; }
- _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return E != b.E; }
+ _FORCE_INLINE_ bool operator==(const Iterator &b) const { return E == b.E; }
+ _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return E != b.E; }
- _FORCE_INLINE_ ConstIterator(const Element *p_E) { E = p_E; }
- _FORCE_INLINE_ ConstIterator() {}
- _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) { E = p_it.E; }
+ Iterator(Element *p_E) { E = p_E; }
+ Iterator() {}
+ Iterator(const Iterator &p_it) { E = p_it.E; }
+
+ operator ConstIterator() const {
+ return ConstIterator(E);
+ }
private:
- const Element *E = nullptr;
+ Element *E = nullptr;
};
_FORCE_INLINE_ Iterator begin() {
@@ -519,7 +523,9 @@ public:
}
}
- T &operator[](int p_index) {
+ // Random access to elements, use with care,
+ // do not use for iteration.
+ T &get(int p_index) {
CRASH_BAD_INDEX(p_index, size());
Element *I = front();
@@ -532,7 +538,9 @@ public:
return I->get();
}
- const T &operator[](int p_index) const {
+ // Random access to elements, use with care,
+ // do not use for iteration.
+ const T &get(int p_index) const {
CRASH_BAD_INDEX(p_index, size());
const Element *I = front();
diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h
index e0047e0782..c281d70d92 100644
--- a/core/templates/local_vector.h
+++ b/core/templates/local_vector.h
@@ -264,6 +264,10 @@ public:
return -1;
}
+ bool has(const T &p_val) const {
+ return find(p_val) != -1;
+ }
+
template <typename C>
void sort_custom() {
U len = count;
diff --git a/core/templates/ring_buffer.h b/core/templates/ring_buffer.h
index 54148a59bf..f5161cefa4 100644
--- a/core/templates/ring_buffer.h
+++ b/core/templates/ring_buffer.h
@@ -211,10 +211,10 @@ public:
size_mask = mask;
}
- RingBuffer<T>(int p_power = 0) {
+ RingBuffer(int p_power = 0) {
resize(p_power);
}
- ~RingBuffer<T>() {}
+ ~RingBuffer() {}
};
#endif // RING_BUFFER_H
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h
index 637b068da9..16b605eaff 100644
--- a/core/templates/safe_refcount.h
+++ b/core/templates/safe_refcount.h
@@ -146,7 +146,7 @@ public:
}
}
- _ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) {
+ _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast<T>(0)) {
set(p_value);
}
};
diff --git a/core/templates/simple_type.h b/core/templates/simple_type.h
index b2ae0110e2..197115ddb9 100644
--- a/core/templates/simple_type.h
+++ b/core/templates/simple_type.h
@@ -31,26 +31,9 @@
#ifndef SIMPLE_TYPE_H
#define SIMPLE_TYPE_H
-/* Batch of specializations to obtain the actual simple type */
+#include <type_traits>
template <typename T>
-struct GetSimpleTypeT {
- typedef T type_t;
-};
-
-template <typename T>
-struct GetSimpleTypeT<T &> {
- typedef T type_t;
-};
-
-template <typename T>
-struct GetSimpleTypeT<T const> {
- typedef T type_t;
-};
-
-template <typename T>
-struct GetSimpleTypeT<T const &> {
- typedef T type_t;
-};
+using GetSimpleTypeT = typename std::remove_cv_t<std::remove_reference_t<T>>;
#endif // SIMPLE_TYPE_H
diff --git a/core/typedefs.h b/core/typedefs.h
index 2b90a911cd..0de803293d 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -71,12 +71,7 @@
#endif
#endif
-// No discard allows the compiler to flag warnings if we don't use the return value of functions / classes
-#ifndef _NO_DISCARD_
-#define _NO_DISCARD_ [[nodiscard]]
-#endif
-
-// In some cases _NO_DISCARD_ will get false positives,
+// In some cases [[nodiscard]] will get false positives,
// we can prevent the warning in specific cases by preceding the call with a cast.
#ifndef _ALLOW_DISCARD_
#define _ALLOW_DISCARD_ (void)
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 3685515db5..54cd1eda2f 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) {
for (int i = 0; i < size; i++) {
const Variant &element = source[i];
if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) {
- ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type)));
}
}
_p->array = p_array._p->array;
@@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) {
continue;
}
if (!Variant::can_convert_strict(value->get_type(), typed.type)) {
- ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'.");
+ ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
Callable::CallError ce;
Variant::construct(typed.type, data[i], &value, 1, ce);
- ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
} else if (Variant::can_convert_strict(source_typed.type, typed.type)) {
// from primitives to different convertible primitives
@@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) {
const Variant *value = source + i;
Callable::CallError ce;
Variant::construct(typed.type, data[i], &value, 1, ce);
- ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
} else {
ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type)));
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 0fe4518b0f..61b90e2a26 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -362,42 +362,42 @@ void call_with_ptr_args_static_method_helper(void (*p_method)(P...), const void
template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_args_helper(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, IndexSequence<Is...>) {
- (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
+ (p_instance->*p_method)((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...);
}
template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_argsc_helper(T *p_instance, void (T::*p_method)(P...) const, const Variant **p_args, IndexSequence<Is...>) {
- (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
+ (p_instance->*p_method)((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...);
}
template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
- VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
+ VariantInternalAccessor<GetSimpleTypeT<R>>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...));
}
template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
- VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
+ VariantInternalAccessor<GetSimpleTypeT<R>>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...));
}
template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_static_retc_helper(T *p_instance, R (*p_method)(T *, P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
- VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, p_method(p_instance, (VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
+ VariantInternalAccessor<GetSimpleTypeT<R>>::set(r_ret, p_method(p_instance, (VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...));
}
template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_args_static_helper(T *p_instance, void (*p_method)(T *, P...), const Variant **p_args, IndexSequence<Is...>) {
- p_method(p_instance, (VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
+ p_method(p_instance, (VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...);
}
template <typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_static_method_ret_helper(R (*p_method)(P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
- VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, p_method((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
+ VariantInternalAccessor<GetSimpleTypeT<R>>::set(r_ret, p_method((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...));
}
template <typename... P, size_t... Is>
void call_with_validated_variant_args_static_method_helper(void (*p_method)(P...), const Variant **p_args, IndexSequence<Is...>) {
- p_method((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
+ p_method((VariantInternalAccessor<GetSimpleTypeT<P>>::get(p_args[Is]))...);
}
template <typename T, typename... P>
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index c6fbfd93a1..667aae879c 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -324,6 +324,7 @@ void Callable::operator=(const Callable &p_callable) {
if (custom->ref_count.unref()) {
memdelete(custom);
+ custom = nullptr;
}
}
@@ -428,6 +429,7 @@ Callable::~Callable() {
if (is_custom()) {
if (custom->ref_count.unref()) {
memdelete(custom);
+ custom = nullptr;
}
}
}
diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h
index 123f2067e2..1e10709b12 100644
--- a/core/variant/method_ptrcall.h
+++ b/core/variant/method_ptrcall.h
@@ -152,6 +152,7 @@ MAKE_PTRARG(PackedStringArray);
MAKE_PTRARG(PackedVector2Array);
MAKE_PTRARG(PackedVector3Array);
MAKE_PTRARG(PackedColorArray);
+MAKE_PTRARG(PackedVector4Array);
MAKE_PTRARG_BY_REFERENCE(Variant);
// This is for Object.
@@ -159,10 +160,7 @@ MAKE_PTRARG_BY_REFERENCE(Variant);
template <typename T>
struct PtrToArg<T *> {
_FORCE_INLINE_ static T *convert(const void *p_ptr) {
- if (p_ptr == nullptr) {
- return nullptr;
- }
- return const_cast<T *>(*reinterpret_cast<T *const *>(p_ptr));
+ return likely(p_ptr) ? const_cast<T *>(*reinterpret_cast<T *const *>(p_ptr)) : nullptr;
}
typedef Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
@@ -173,10 +171,7 @@ struct PtrToArg<T *> {
template <typename T>
struct PtrToArg<const T *> {
_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
- if (p_ptr == nullptr) {
- return nullptr;
- }
- return *reinterpret_cast<T *const *>(p_ptr);
+ return likely(p_ptr) ? *reinterpret_cast<T *const *>(p_ptr) : nullptr;
}
typedef const Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
diff --git a/core/variant/type_info.h b/core/variant/type_info.h
index 9c52db3345..d51c80eebe 100644
--- a/core/variant/type_info.h
+++ b/core/variant/type_info.h
@@ -33,31 +33,7 @@
#include "core/typedefs.h"
-template <bool C, typename T = void>
-struct EnableIf {
- typedef T type;
-};
-
-template <typename T>
-struct EnableIf<false, T> {
-};
-
-template <typename, typename>
-inline constexpr bool types_are_same_v = false;
-
-template <typename T>
-inline constexpr bool types_are_same_v<T, T> = true;
-
-template <typename B, typename D>
-struct TypeInherits {
- static D *get_d();
-
- static char (&test(B *))[1];
- static char (&test(...))[2];
-
- static bool const value = sizeof(test(get_d())) == sizeof(char) &&
- !types_are_same_v<B volatile const, void volatile const>;
-};
+#include <type_traits>
namespace GodotTypeInfo {
enum Metadata {
@@ -166,6 +142,7 @@ MAKE_TYPE_INFO(PackedStringArray, Variant::PACKED_STRING_ARRAY)
MAKE_TYPE_INFO(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPE_INFO(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPE_INFO(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPE_INFO(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
MAKE_TYPE_INFO(IPAddress, Variant::STRING)
@@ -223,16 +200,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Face3, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::PACKED_STRING_ARRAY)
template <typename T>
-struct GetTypeInfo<T *, typename EnableIf<TypeInherits<Object, T>::value>::type> {
- static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
- static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
- static inline PropertyInfo get_class_info() {
- return PropertyInfo(StringName(T::get_class_static()));
- }
-};
-
-template <typename T>
-struct GetTypeInfo<const T *, typename EnableIf<TypeInherits<Object, T>::value>::type> {
+struct GetTypeInfo<T *, std::enable_if_t<std::is_base_of_v<Object, T>>> {
static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h
index 0befd19864..07bf8afa7b 100644
--- a/core/variant/typed_array.h
+++ b/core/variant/typed_array.h
@@ -46,10 +46,15 @@ public:
_ref(p_array);
}
_FORCE_INLINE_ TypedArray(const Variant &p_variant) :
- Array(Array(p_variant), Variant::OBJECT, T::get_class_static(), Variant()) {
+ TypedArray(Array(p_variant)) {
}
- _FORCE_INLINE_ TypedArray(const Array &p_array) :
- Array(p_array, Variant::OBJECT, T::get_class_static(), Variant()) {
+ _FORCE_INLINE_ TypedArray(const Array &p_array) {
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant());
+ if (is_same_typed(p_array)) {
+ _ref(p_array);
+ } else {
+ assign(p_array);
+ }
}
_FORCE_INLINE_ TypedArray() {
set_typed(Variant::OBJECT, T::get_class_static(), Variant());
@@ -78,10 +83,15 @@ struct VariantInternalAccessor<const TypedArray<T> &> {
_ref(p_array); \
} \
_FORCE_INLINE_ TypedArray(const Variant &p_variant) : \
- Array(Array(p_variant), m_variant_type, StringName(), Variant()) { \
+ TypedArray(Array(p_variant)) { \
} \
- _FORCE_INLINE_ TypedArray(const Array &p_array) : \
- Array(p_array, m_variant_type, StringName(), Variant()) { \
+ _FORCE_INLINE_ TypedArray(const Array &p_array) { \
+ set_typed(m_variant_type, StringName(), Variant()); \
+ if (is_same_typed(p_array)) { \
+ _ref(p_array); \
+ } else { \
+ assign(p_array); \
+ } \
} \
_FORCE_INLINE_ TypedArray() { \
set_typed(m_variant_type, StringName(), Variant()); \
@@ -134,6 +144,7 @@ MAKE_TYPED_ARRAY(PackedStringArray, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_ARRAY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
MAKE_TYPED_ARRAY(IPAddress, Variant::STRING)
template <typename T>
@@ -235,6 +246,7 @@ MAKE_TYPED_ARRAY_INFO(PackedStringArray, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_ARRAY_INFO(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
MAKE_TYPED_ARRAY_INFO(IPAddress, Variant::STRING)
#undef MAKE_TYPED_ARRAY
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 155a5b2781..c1ef31c784 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -30,7 +30,6 @@
#include "variant.h"
-#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/json.h"
#include "core/io/marshalls.h"
@@ -167,6 +166,9 @@ String Variant::get_type_name(Variant::Type p_type) {
case PACKED_COLOR_ARRAY: {
return "PackedColorArray";
}
+ case PACKED_VECTOR4_ARRAY: {
+ return "PackedVector4Array";
+ }
default: {
}
}
@@ -404,6 +406,7 @@ bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
PACKED_COLOR_ARRAY,
PACKED_VECTOR2_ARRAY,
PACKED_VECTOR3_ARRAY,
+ PACKED_VECTOR4_ARRAY,
NIL
};
@@ -480,6 +483,14 @@ bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
valid_types = valid;
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ static const Type valid[] = {
+ ARRAY,
+ NIL
+ };
+ valid_types = valid;
+
+ } break;
default: {
}
}
@@ -738,6 +749,7 @@ bool Variant::can_convert_strict(Variant::Type p_type_from, Variant::Type p_type
PACKED_COLOR_ARRAY,
PACKED_VECTOR2_ARRAY,
PACKED_VECTOR3_ARRAY,
+ PACKED_VECTOR4_ARRAY,
NIL
};
@@ -814,6 +826,14 @@ bool Variant::can_convert_strict(Variant::Type p_type_from, Variant::Type p_type
valid_types = valid;
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ static const Type valid[] = {
+ ARRAY,
+ NIL
+ };
+ valid_types = valid;
+
+ } break;
default: {
}
}
@@ -931,7 +951,7 @@ bool Variant::is_zero() const {
return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID();
}
case OBJECT: {
- return get_validated_object() == nullptr;
+ return _get_obj().obj == nullptr;
}
case CALLABLE: {
return reinterpret_cast<const Callable *>(_data._mem)->is_null();
@@ -980,6 +1000,9 @@ bool Variant::is_zero() const {
case PACKED_COLOR_ARRAY: {
return PackedArrayRef<Color>::get_array(_data.packed_array).size() == 0;
}
+ case PACKED_VECTOR4_ARRAY: {
+ return PackedArrayRef<Vector4>::get_array(_data.packed_array).size() == 0;
+ }
default: {
}
}
@@ -1236,6 +1259,12 @@ void Variant::reference(const Variant &p_variant) {
_data.packed_array = PackedArrayRef<Color>::create();
}
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ _data.packed_array = static_cast<PackedArrayRef<Vector4> *>(p_variant._data.packed_array)->reference();
+ if (!_data.packed_array) {
+ _data.packed_array = PackedArrayRef<Vector4>::create();
+ }
+ } break;
default: {
}
}
@@ -1410,9 +1439,12 @@ void Variant::_clear_internal() {
case PACKED_COLOR_ARRAY: {
PackedArrayRefBase::destroy(_data.packed_array);
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ PackedArrayRefBase::destroy(_data.packed_array);
+ } break;
default: {
// Not needed, there is no point. The following do not allocate memory:
- // VECTOR2, VECTOR3, RECT2, PLANE, QUATERNION, COLOR.
+ // VECTOR2, VECTOR3, VECTOR4, RECT2, PLANE, QUATERNION, COLOR.
}
}
}
@@ -1759,6 +1791,9 @@ String Variant::stringify(int recursion_count) const {
case PACKED_COLOR_ARRAY: {
return stringify_vector(operator PackedColorArray(), recursion_count);
}
+ case PACKED_VECTOR4_ARRAY: {
+ return stringify_vector(operator PackedVector4Array(), recursion_count);
+ }
case PACKED_STRING_ARRAY: {
return stringify_vector(operator PackedStringArray(), recursion_count);
}
@@ -2085,7 +2120,7 @@ Variant::operator ::RID() const {
}
#endif
Callable::CallError ce;
- Variant ret = _get_obj().obj->callp(CoreStringNames::get_singleton()->get_rid, nullptr, 0, ce);
+ Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) {
return ret;
}
@@ -2191,6 +2226,9 @@ inline DA _convert_array_from_variant(const Variant &p_variant) {
case Variant::PACKED_COLOR_ARRAY: {
return _convert_array<DA, PackedColorArray>(p_variant.operator PackedColorArray());
}
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ return _convert_array<DA, PackedVector4Array>(p_variant.operator PackedVector4Array());
+ }
default: {
return DA();
}
@@ -2277,6 +2315,14 @@ Variant::operator PackedColorArray() const {
}
}
+Variant::operator PackedVector4Array() const {
+ if (type == PACKED_VECTOR4_ARRAY) {
+ return static_cast<PackedArrayRef<Vector4> *>(_data.packed_array)->array;
+ } else {
+ return _convert_array_from_variant<PackedVector4Array>(*this);
+ }
+}
+
/* helpers */
Variant::operator Vector<::RID>() const {
@@ -2373,184 +2419,183 @@ Variant::operator IPAddress() const {
return IPAddress(operator String());
}
-Variant::Variant(bool p_bool) {
- type = BOOL;
+Variant::Variant(bool p_bool) :
+ type(BOOL) {
_data._bool = p_bool;
}
-Variant::Variant(int64_t p_int64) {
- type = INT;
+Variant::Variant(int64_t p_int64) :
+ type(INT) {
_data._int = p_int64;
}
-Variant::Variant(int32_t p_int32) {
- type = INT;
+Variant::Variant(int32_t p_int32) :
+ type(INT) {
_data._int = p_int32;
}
-Variant::Variant(int16_t p_int16) {
- type = INT;
+Variant::Variant(int16_t p_int16) :
+ type(INT) {
_data._int = p_int16;
}
-Variant::Variant(int8_t p_int8) {
- type = INT;
+Variant::Variant(int8_t p_int8) :
+ type(INT) {
_data._int = p_int8;
}
-Variant::Variant(uint64_t p_uint64) {
- type = INT;
+Variant::Variant(uint64_t p_uint64) :
+ type(INT) {
_data._int = p_uint64;
}
-Variant::Variant(uint32_t p_uint32) {
- type = INT;
+Variant::Variant(uint32_t p_uint32) :
+ type(INT) {
_data._int = p_uint32;
}
-Variant::Variant(uint16_t p_uint16) {
- type = INT;
+Variant::Variant(uint16_t p_uint16) :
+ type(INT) {
_data._int = p_uint16;
}
-Variant::Variant(uint8_t p_uint8) {
- type = INT;
+Variant::Variant(uint8_t p_uint8) :
+ type(INT) {
_data._int = p_uint8;
}
-Variant::Variant(float p_float) {
- type = FLOAT;
+Variant::Variant(float p_float) :
+ type(FLOAT) {
_data._float = p_float;
}
-Variant::Variant(double p_double) {
- type = FLOAT;
+Variant::Variant(double p_double) :
+ type(FLOAT) {
_data._float = p_double;
}
-Variant::Variant(const ObjectID &p_id) {
- type = INT;
+Variant::Variant(const ObjectID &p_id) :
+ type(INT) {
_data._int = p_id;
}
-Variant::Variant(const StringName &p_string) {
- type = STRING_NAME;
+Variant::Variant(const StringName &p_string) :
+ type(STRING_NAME) {
memnew_placement(_data._mem, StringName(p_string));
}
-Variant::Variant(const String &p_string) {
- type = STRING;
+Variant::Variant(const String &p_string) :
+ type(STRING) {
memnew_placement(_data._mem, String(p_string));
}
-Variant::Variant(const char *const p_cstring) {
- type = STRING;
+Variant::Variant(const char *const p_cstring) :
+ type(STRING) {
memnew_placement(_data._mem, String((const char *)p_cstring));
}
-Variant::Variant(const char32_t *p_wstring) {
- type = STRING;
+Variant::Variant(const char32_t *p_wstring) :
+ type(STRING) {
memnew_placement(_data._mem, String(p_wstring));
}
-Variant::Variant(const Vector3 &p_vector3) {
- type = VECTOR3;
+Variant::Variant(const Vector3 &p_vector3) :
+ type(VECTOR3) {
memnew_placement(_data._mem, Vector3(p_vector3));
}
-Variant::Variant(const Vector3i &p_vector3i) {
- type = VECTOR3I;
+Variant::Variant(const Vector3i &p_vector3i) :
+ type(VECTOR3I) {
memnew_placement(_data._mem, Vector3i(p_vector3i));
}
-Variant::Variant(const Vector4 &p_vector4) {
- type = VECTOR4;
+Variant::Variant(const Vector4 &p_vector4) :
+ type(VECTOR4) {
memnew_placement(_data._mem, Vector4(p_vector4));
}
-Variant::Variant(const Vector4i &p_vector4i) {
- type = VECTOR4I;
+Variant::Variant(const Vector4i &p_vector4i) :
+ type(VECTOR4I) {
memnew_placement(_data._mem, Vector4i(p_vector4i));
}
-Variant::Variant(const Vector2 &p_vector2) {
- type = VECTOR2;
+Variant::Variant(const Vector2 &p_vector2) :
+ type(VECTOR2) {
memnew_placement(_data._mem, Vector2(p_vector2));
}
-Variant::Variant(const Vector2i &p_vector2i) {
- type = VECTOR2I;
+Variant::Variant(const Vector2i &p_vector2i) :
+ type(VECTOR2I) {
memnew_placement(_data._mem, Vector2i(p_vector2i));
}
-Variant::Variant(const Rect2 &p_rect2) {
- type = RECT2;
+Variant::Variant(const Rect2 &p_rect2) :
+ type(RECT2) {
memnew_placement(_data._mem, Rect2(p_rect2));
}
-Variant::Variant(const Rect2i &p_rect2i) {
- type = RECT2I;
+Variant::Variant(const Rect2i &p_rect2i) :
+ type(RECT2I) {
memnew_placement(_data._mem, Rect2i(p_rect2i));
}
-Variant::Variant(const Plane &p_plane) {
- type = PLANE;
+Variant::Variant(const Plane &p_plane) :
+ type(PLANE) {
memnew_placement(_data._mem, Plane(p_plane));
}
-Variant::Variant(const ::AABB &p_aabb) {
- type = AABB;
+Variant::Variant(const ::AABB &p_aabb) :
+ type(AABB) {
_data._aabb = (::AABB *)Pools::_bucket_small.alloc();
memnew_placement(_data._aabb, ::AABB(p_aabb));
}
-Variant::Variant(const Basis &p_matrix) {
- type = BASIS;
+Variant::Variant(const Basis &p_matrix) :
+ type(BASIS) {
_data._basis = (Basis *)Pools::_bucket_medium.alloc();
memnew_placement(_data._basis, Basis(p_matrix));
}
-Variant::Variant(const Quaternion &p_quaternion) {
- type = QUATERNION;
+Variant::Variant(const Quaternion &p_quaternion) :
+ type(QUATERNION) {
memnew_placement(_data._mem, Quaternion(p_quaternion));
}
-Variant::Variant(const Transform3D &p_transform) {
- type = TRANSFORM3D;
+Variant::Variant(const Transform3D &p_transform) :
+ type(TRANSFORM3D) {
_data._transform3d = (Transform3D *)Pools::_bucket_medium.alloc();
memnew_placement(_data._transform3d, Transform3D(p_transform));
}
-Variant::Variant(const Projection &pp_projection) {
- type = PROJECTION;
+Variant::Variant(const Projection &pp_projection) :
+ type(PROJECTION) {
_data._projection = (Projection *)Pools::_bucket_large.alloc();
memnew_placement(_data._projection, Projection(pp_projection));
}
-Variant::Variant(const Transform2D &p_transform) {
- type = TRANSFORM2D;
+Variant::Variant(const Transform2D &p_transform) :
+ type(TRANSFORM2D) {
_data._transform2d = (Transform2D *)Pools::_bucket_small.alloc();
memnew_placement(_data._transform2d, Transform2D(p_transform));
}
-Variant::Variant(const Color &p_color) {
- type = COLOR;
+Variant::Variant(const Color &p_color) :
+ type(COLOR) {
memnew_placement(_data._mem, Color(p_color));
}
-Variant::Variant(const NodePath &p_node_path) {
- type = NODE_PATH;
+Variant::Variant(const NodePath &p_node_path) :
+ type(NODE_PATH) {
memnew_placement(_data._mem, NodePath(p_node_path));
}
-Variant::Variant(const ::RID &p_rid) {
- type = RID;
+Variant::Variant(const ::RID &p_rid) :
+ type(RID) {
memnew_placement(_data._mem, ::RID(p_rid));
}
-Variant::Variant(const Object *p_object) {
- type = OBJECT;
-
+Variant::Variant(const Object *p_object) :
+ type(OBJECT) {
memnew_placement(_data._mem, ObjData);
if (p_object) {
@@ -2571,76 +2616,79 @@ Variant::Variant(const Object *p_object) {
}
}
-Variant::Variant(const Callable &p_callable) {
- type = CALLABLE;
+Variant::Variant(const Callable &p_callable) :
+ type(CALLABLE) {
memnew_placement(_data._mem, Callable(p_callable));
}
-Variant::Variant(const Signal &p_callable) {
- type = SIGNAL;
+Variant::Variant(const Signal &p_callable) :
+ type(SIGNAL) {
memnew_placement(_data._mem, Signal(p_callable));
}
-Variant::Variant(const Dictionary &p_dictionary) {
- type = DICTIONARY;
+Variant::Variant(const Dictionary &p_dictionary) :
+ type(DICTIONARY) {
memnew_placement(_data._mem, Dictionary(p_dictionary));
}
-Variant::Variant(const Array &p_array) {
- type = ARRAY;
+Variant::Variant(const Array &p_array) :
+ type(ARRAY) {
memnew_placement(_data._mem, Array(p_array));
}
-Variant::Variant(const PackedByteArray &p_byte_array) {
- type = PACKED_BYTE_ARRAY;
-
+Variant::Variant(const PackedByteArray &p_byte_array) :
+ type(PACKED_BYTE_ARRAY) {
_data.packed_array = PackedArrayRef<uint8_t>::create(p_byte_array);
}
-Variant::Variant(const PackedInt32Array &p_int32_array) {
- type = PACKED_INT32_ARRAY;
+Variant::Variant(const PackedInt32Array &p_int32_array) :
+ type(PACKED_INT32_ARRAY) {
_data.packed_array = PackedArrayRef<int32_t>::create(p_int32_array);
}
-Variant::Variant(const PackedInt64Array &p_int64_array) {
- type = PACKED_INT64_ARRAY;
+Variant::Variant(const PackedInt64Array &p_int64_array) :
+ type(PACKED_INT64_ARRAY) {
_data.packed_array = PackedArrayRef<int64_t>::create(p_int64_array);
}
-Variant::Variant(const PackedFloat32Array &p_float32_array) {
- type = PACKED_FLOAT32_ARRAY;
+Variant::Variant(const PackedFloat32Array &p_float32_array) :
+ type(PACKED_FLOAT32_ARRAY) {
_data.packed_array = PackedArrayRef<float>::create(p_float32_array);
}
-Variant::Variant(const PackedFloat64Array &p_float64_array) {
- type = PACKED_FLOAT64_ARRAY;
+Variant::Variant(const PackedFloat64Array &p_float64_array) :
+ type(PACKED_FLOAT64_ARRAY) {
_data.packed_array = PackedArrayRef<double>::create(p_float64_array);
}
-Variant::Variant(const PackedStringArray &p_string_array) {
- type = PACKED_STRING_ARRAY;
+Variant::Variant(const PackedStringArray &p_string_array) :
+ type(PACKED_STRING_ARRAY) {
_data.packed_array = PackedArrayRef<String>::create(p_string_array);
}
-Variant::Variant(const PackedVector2Array &p_vector2_array) {
- type = PACKED_VECTOR2_ARRAY;
+Variant::Variant(const PackedVector2Array &p_vector2_array) :
+ type(PACKED_VECTOR2_ARRAY) {
_data.packed_array = PackedArrayRef<Vector2>::create(p_vector2_array);
}
-Variant::Variant(const PackedVector3Array &p_vector3_array) {
- type = PACKED_VECTOR3_ARRAY;
+Variant::Variant(const PackedVector3Array &p_vector3_array) :
+ type(PACKED_VECTOR3_ARRAY) {
_data.packed_array = PackedArrayRef<Vector3>::create(p_vector3_array);
}
-Variant::Variant(const PackedColorArray &p_color_array) {
- type = PACKED_COLOR_ARRAY;
+Variant::Variant(const PackedColorArray &p_color_array) :
+ type(PACKED_COLOR_ARRAY) {
_data.packed_array = PackedArrayRef<Color>::create(p_color_array);
}
-/* helpers */
-Variant::Variant(const Vector<::RID> &p_array) {
- type = ARRAY;
+Variant::Variant(const PackedVector4Array &p_vector4_array) :
+ type(PACKED_VECTOR4_ARRAY) {
+ _data.packed_array = PackedArrayRef<Vector4>::create(p_vector4_array);
+}
+/* helpers */
+Variant::Variant(const Vector<::RID> &p_array) :
+ type(ARRAY) {
Array *rid_array = memnew_placement(_data._mem, Array);
rid_array->resize(p_array.size());
@@ -2650,9 +2698,8 @@ Variant::Variant(const Vector<::RID> &p_array) {
}
}
-Variant::Variant(const Vector<Plane> &p_array) {
- type = ARRAY;
-
+Variant::Variant(const Vector<Plane> &p_array) :
+ type(ARRAY) {
Array *plane_array = memnew_placement(_data._mem, Array);
plane_array->resize(p_array.size());
@@ -2662,7 +2709,8 @@ Variant::Variant(const Vector<Plane> &p_array) {
}
}
-Variant::Variant(const Vector<Face3> &p_face_array) {
+Variant::Variant(const Vector<Face3> &p_face_array) :
+ type(NIL) {
PackedVector3Array vertices;
int face_count = p_face_array.size();
vertices.resize(face_count * 3);
@@ -2678,13 +2726,11 @@ Variant::Variant(const Vector<Face3> &p_face_array) {
}
}
- type = NIL;
-
*this = vertices;
}
-Variant::Variant(const Vector<Variant> &p_array) {
- type = NIL;
+Variant::Variant(const Vector<Variant> &p_array) :
+ type(NIL) {
Array arr;
arr.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
@@ -2693,8 +2739,8 @@ 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) :
+ type(NIL) {
PackedStringArray v;
int len = p_array.size();
v.resize(len);
@@ -2858,17 +2904,21 @@ void Variant::operator=(const Variant &p_variant) {
case PACKED_COLOR_ARRAY: {
_data.packed_array = PackedArrayRef<Color>::reference_from(_data.packed_array, p_variant._data.packed_array);
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ _data.packed_array = PackedArrayRef<Vector4>::reference_from(_data.packed_array, p_variant._data.packed_array);
+ } break;
default: {
}
}
}
-Variant::Variant(const IPAddress &p_address) {
- type = STRING;
+Variant::Variant(const IPAddress &p_address) :
+ type(STRING) {
memnew_placement(_data._mem, String(p_address));
}
-Variant::Variant(const Variant &p_variant) {
+Variant::Variant(const Variant &p_variant) :
+ type(NIL) {
reference(p_variant);
}
@@ -3179,6 +3229,25 @@ uint32_t Variant::recursive_hash(int recursion_count) const {
return hash;
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ uint32_t hash = HASH_MURMUR3_SEED;
+ const PackedVector4Array &arr = PackedArrayRef<Vector4>::get_array(_data.packed_array);
+ int len = arr.size();
+
+ if (likely(len)) {
+ const Vector4 *r = arr.ptr();
+
+ for (int i = 0; i < len; i++) {
+ hash = hash_murmur3_one_real(r[i].x, hash);
+ hash = hash_murmur3_one_real(r[i].y, hash);
+ hash = hash_murmur3_one_real(r[i].z, hash);
+ hash = hash_murmur3_one_real(r[i].w, hash);
+ }
+ hash = hash_fmix32(hash);
+ }
+
+ return hash;
+ } break;
default: {
}
}
@@ -3434,6 +3503,10 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count, bool s
hash_compare_packed_array(_data.packed_array, p_variant._data.packed_array, Color, hash_compare_color);
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ hash_compare_packed_array(_data.packed_array, p_variant._data.packed_array, Vector4, hash_compare_vector4);
+ } break;
+
default:
bool v;
Variant r;
@@ -3472,7 +3545,8 @@ bool Variant::identity_compare(const Variant &p_variant) const {
case PACKED_STRING_ARRAY:
case PACKED_VECTOR2_ARRAY:
case PACKED_VECTOR3_ARRAY:
- case PACKED_COLOR_ARRAY: {
+ case PACKED_COLOR_ARRAY:
+ case PACKED_VECTOR4_ARRAY: {
return _data.packed_array == p_variant._data.packed_array;
} break;
@@ -3499,50 +3573,6 @@ bool Variant::is_ref_counted() const {
return type == OBJECT && _get_obj().id.is_ref_counted();
}
-Vector<Variant> varray() {
- return Vector<Variant>();
-}
-
-Vector<Variant> varray(const Variant &p_arg1) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- v.push_back(p_arg4);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- v.push_back(p_arg4);
- v.push_back(p_arg5);
- return v;
-}
-
void Variant::static_assign(const Variant &p_variant) {
}
@@ -3563,6 +3593,17 @@ bool Variant::is_shared() const {
return is_type_shared(type);
}
+bool Variant::is_read_only() const {
+ switch (type) {
+ case ARRAY:
+ return reinterpret_cast<const Array *>(_data._mem)->is_read_only();
+ case DICTIONARY:
+ return reinterpret_cast<const Dictionary *>(_data._mem)->is_read_only();
+ default:
+ return false;
+ }
+}
+
void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) {
switch (error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 10f8dc3c7f..1cb3580c01 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -31,6 +31,7 @@
#ifndef VARIANT_H
#define VARIANT_H
+#include "core/core_string_names.h"
#include "core/input/input_enums.h"
#include "core/io/ip_address.h"
#include "core/math/aabb.h"
@@ -75,6 +76,7 @@ typedef Vector<String> PackedStringArray;
typedef Vector<Vector2> PackedVector2Array;
typedef Vector<Vector3> PackedVector3Array;
typedef Vector<Color> PackedColorArray;
+typedef Vector<Vector4> PackedVector4Array;
class Variant {
public:
@@ -126,6 +128,7 @@ public:
PACKED_VECTOR2_ARRAY,
PACKED_VECTOR3_ARRAY,
PACKED_COLOR_ARRAY,
+ PACKED_VECTOR4_ARRAY,
VARIANT_MAX
};
@@ -162,8 +165,10 @@ private:
friend struct _VariantCall;
friend class VariantInternal;
- // Variant takes 20 bytes when real_t is float, and 36 if double
- // it only allocates extra memory for aabb/matrix.
+ // Variant takes 24 bytes when real_t is float, and 40 bytes if double.
+ // It only allocates extra memory for AABB/Transform2D (24, 48 if double),
+ // Basis/Transform3D (48, 96 if double), Projection (64, 128 if double),
+ // and PackedArray/Array/Dictionary (platform-dependent).
Type type = NIL;
@@ -297,6 +302,7 @@ private:
true, //PACKED_VECTOR2_ARRAY,
true, //PACKED_VECTOR3_ARRAY,
true, //PACKED_COLOR_ARRAY,
+ true, //PACKED_VECTOR4_ARRAY,
};
if (unlikely(needs_deinit[type])) { // Make it fast for types that don't need deinit.
@@ -349,6 +355,7 @@ public:
bool is_zero() const;
bool is_one() const;
bool is_null() const;
+ bool is_read_only() const;
// Make sure Variant is not implicitly cast when accessing it with bracket notation (GH-49469).
Variant &operator[](const Variant &p_key) = delete;
@@ -408,6 +415,7 @@ public:
operator PackedVector3Array() const;
operator PackedVector2Array() const;
operator PackedColorArray() const;
+ operator PackedVector4Array() const;
operator Vector<::RID>() const;
operator Vector<Plane>() const;
@@ -473,6 +481,7 @@ public:
Variant(const PackedVector2Array &p_vector2_array);
Variant(const PackedVector3Array &p_vector3_array);
Variant(const PackedColorArray &p_color_array);
+ Variant(const PackedVector4Array &p_vector4_array);
Variant(const Vector<::RID> &p_array); // helper
Variant(const Vector<Plane> &p_array); // helper
@@ -483,8 +492,8 @@ public:
Variant(const IPAddress &p_address);
#define VARIANT_ENUM_CLASS_CONSTRUCTOR(m_enum) \
- Variant(m_enum p_value) { \
- type = INT; \
+ Variant(m_enum p_value) : \
+ type(INT) { \
_data._int = (int64_t)p_value; \
}
@@ -788,7 +797,8 @@ public:
static void unregister_types();
Variant(const Variant &p_variant);
- _FORCE_INLINE_ Variant() {}
+ _FORCE_INLINE_ Variant() :
+ type(NIL) {}
_FORCE_INLINE_ ~Variant() {
clear();
}
@@ -797,12 +807,23 @@ public:
//typedef Dictionary Dictionary; no
//typedef Array Array;
-Vector<Variant> varray();
-Vector<Variant> varray(const Variant &p_arg1);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5);
+template <typename... VarArgs>
+Vector<Variant> varray(VarArgs... p_args) {
+ Vector<Variant> v;
+
+ Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
+ uint32_t argc = sizeof...(p_args);
+
+ if (argc > 0) {
+ v.resize(argc);
+ Variant *vw = v.ptrw();
+
+ for (uint32_t i = 0; i < argc; i++) {
+ vw[i] = args[i];
+ }
+ }
+ return v;
+}
struct VariantHasher {
static _FORCE_INLINE_ uint32_t hash(const Variant &p_variant) { return p_variant.hash(); }
@@ -836,7 +857,7 @@ String vformat(const String &p_text, const VarArgs... p_args) {
bool error = false;
String fmt = p_text.sprintf(args_array, &error);
- ERR_FAIL_COND_V_MSG(error, String(), fmt);
+ ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + ".");
return fmt;
}
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index d0d940c47d..5e402937cf 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -30,7 +30,6 @@
#include "variant.h"
-#include "core/core_string_names.h"
#include "core/crypto/crypto_core.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/compression.h"
@@ -1632,7 +1631,7 @@ int Variant::get_enum_value(Variant::Type p_type, const StringName &p_enum_name,
VARARG_CLASS1(m_type, m_name, m_method, m_arg_type) \
register_builtin_method<Method_##m_type##_##m_name>(sarray(m_arg_name), Vector<Variant>());
-static void _register_variant_builtin_methods() {
+static void _register_variant_builtin_methods_string() {
_VariantCall::constant_data = memnew_arr(_VariantCall::ConstantData, Variant::VARIANT_MAX);
_VariantCall::enum_data = memnew_arr(_VariantCall::EnumData, Variant::VARIANT_MAX);
builtin_method_info = memnew_arr(BuiltinMethodMap, Variant::VARIANT_MAX);
@@ -1648,19 +1647,19 @@ static void _register_variant_builtin_methods() {
bind_string_method(filenocasecmp_to, sarray("to"), varray());
bind_string_method(length, sarray(), varray());
bind_string_method(substr, sarray("from", "len"), varray(-1));
- bind_string_method(get_slice, sarray("delimiter", "slice"), varray());
+ bind_string_methodv(get_slice, static_cast<String (String::*)(const String &, int) const>(&String::get_slice), sarray("delimiter", "slice"), varray());
bind_string_method(get_slicec, sarray("delimiter", "slice"), varray());
- bind_string_method(get_slice_count, sarray("delimiter"), varray());
+ bind_string_methodv(get_slice_count, static_cast<int (String::*)(const String &) const>(&String::get_slice_count), sarray("delimiter"), varray());
bind_string_methodv(find, static_cast<int (String::*)(const String &, int) const>(&String::find), sarray("what", "from"), varray(0));
- bind_string_method(count, sarray("what", "from", "to"), varray(0, 0));
- bind_string_method(countn, sarray("what", "from", "to"), varray(0, 0));
- bind_string_method(findn, sarray("what", "from"), varray(0));
- bind_string_method(rfind, sarray("what", "from"), varray(-1));
- bind_string_method(rfindn, sarray("what", "from"), varray(-1));
+ bind_string_methodv(findn, static_cast<int (String::*)(const String &, int) const>(&String::findn), sarray("what", "from"), varray(0));
+ bind_string_methodv(count, static_cast<int (String::*)(const String &, int, int) const>(&String::count), sarray("what", "from", "to"), varray(0, 0));
+ bind_string_methodv(countn, static_cast<int (String::*)(const String &, int, int) const>(&String::countn), sarray("what", "from", "to"), varray(0, 0));
+ bind_string_methodv(rfind, static_cast<int (String::*)(const String &, int) const>(&String::rfind), sarray("what", "from"), varray(-1));
+ bind_string_methodv(rfindn, static_cast<int (String::*)(const String &, int) const>(&String::rfindn), sarray("what", "from"), varray(-1));
bind_string_method(match, sarray("expr"), varray());
bind_string_method(matchn, sarray("expr"), varray());
bind_string_methodv(begins_with, static_cast<bool (String::*)(const String &) const>(&String::begins_with), sarray("text"), varray());
- bind_string_method(ends_with, sarray("text"), varray());
+ bind_string_methodv(ends_with, static_cast<bool (String::*)(const String &) const>(&String::ends_with), sarray("text"), varray());
bind_string_method(is_subsequence_of, sarray("text"), varray());
bind_string_method(is_subsequence_ofn, sarray("text"), varray());
bind_string_method(bigrams, sarray(), varray());
@@ -1668,7 +1667,7 @@ static void _register_variant_builtin_methods() {
bind_string_method(format, sarray("values", "placeholder"), varray("{_}"));
bind_string_methodv(replace, static_cast<String (String::*)(const String &, const String &) const>(&String::replace), sarray("what", "forwhat"), varray());
- bind_string_method(replacen, sarray("what", "forwhat"), varray());
+ bind_string_methodv(replacen, static_cast<String (String::*)(const String &, const String &) const>(&String::replacen), sarray("what", "forwhat"), varray());
bind_string_method(repeat, sarray("count"), varray());
bind_string_method(reverse, sarray(), varray());
bind_string_method(insert, sarray("position", "what"), varray());
@@ -1677,8 +1676,8 @@ static void _register_variant_builtin_methods() {
bind_string_method(to_camel_case, sarray(), varray());
bind_string_method(to_pascal_case, sarray(), varray());
bind_string_method(to_snake_case, sarray(), varray());
- bind_string_method(split, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0));
- bind_string_method(rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0));
+ bind_string_methodv(split, static_cast<Vector<String> (String::*)(const String &, bool, int) const>(&String::split), sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0));
+ bind_string_methodv(rsplit, static_cast<Vector<String> (String::*)(const String &, bool, int) const>(&String::rsplit), sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0));
bind_string_method(split_floats, sarray("delimiter", "allow_empty"), varray(true));
bind_string_method(join, sarray("parts"), varray());
@@ -1707,6 +1706,7 @@ static void _register_variant_builtin_methods() {
bind_string_method(sha256_buffer, sarray(), varray());
bind_string_method(is_empty, sarray(), varray());
bind_string_methodv(contains, static_cast<bool (String::*)(const String &) const>(&String::contains), sarray("what"), varray());
+ bind_string_methodv(containsn, static_cast<bool (String::*)(const String &) const>(&String::containsn), sarray("what"), varray());
bind_string_method(is_absolute_path, sarray(), varray());
bind_string_method(is_relative_path, sarray(), varray());
@@ -1741,8 +1741,8 @@ static void _register_variant_builtin_methods() {
bind_string_method(rpad, sarray("min_length", "character"), varray(" "));
bind_string_method(pad_decimals, sarray("digits"), varray());
bind_string_method(pad_zeros, sarray("digits"), varray());
- bind_string_method(trim_prefix, sarray("prefix"), varray());
- bind_string_method(trim_suffix, sarray("suffix"), varray());
+ bind_string_methodv(trim_prefix, static_cast<String (String::*)(const String &) const>(&String::trim_prefix), sarray("prefix"), varray());
+ bind_string_methodv(trim_suffix, static_cast<String (String::*)(const String &) const>(&String::trim_suffix), sarray("suffix"), varray());
bind_string_method(to_ascii_buffer, sarray(), varray());
bind_string_method(to_utf8_buffer, sarray(), varray());
@@ -1761,7 +1761,9 @@ static void _register_variant_builtin_methods() {
/* StringName */
bind_method(StringName, hash, sarray(), varray());
+}
+static void _register_variant_builtin_methods_math() {
/* Vector2 */
bind_method(Vector2, angle, sarray(), varray());
@@ -1804,7 +1806,13 @@ static void _register_variant_builtin_methods() {
bind_method(Vector2, abs, sarray(), varray());
bind_method(Vector2, sign, sarray(), varray());
bind_method(Vector2, clamp, sarray("min", "max"), varray());
+ bind_method(Vector2, clampf, sarray("min", "max"), varray());
bind_method(Vector2, snapped, sarray("step"), varray());
+ bind_method(Vector2, snappedf, sarray("step"), varray());
+ bind_method(Vector2, min, sarray("with"), varray());
+ bind_method(Vector2, minf, sarray("with"), varray());
+ bind_method(Vector2, max, sarray("with"), varray());
+ bind_method(Vector2, maxf, sarray("with"), varray());
bind_static_method(Vector2, from_angle, sarray("angle"), varray());
@@ -1820,7 +1828,13 @@ static void _register_variant_builtin_methods() {
bind_method(Vector2i, sign, sarray(), varray());
bind_method(Vector2i, abs, sarray(), varray());
bind_method(Vector2i, clamp, sarray("min", "max"), varray());
+ bind_method(Vector2i, clampi, sarray("min", "max"), varray());
bind_method(Vector2i, snapped, sarray("step"), varray());
+ bind_method(Vector2i, snappedi, sarray("step"), varray());
+ bind_method(Vector2i, min, sarray("with"), varray());
+ bind_method(Vector2i, mini, sarray("with"), varray());
+ bind_method(Vector2i, max, sarray("with"), varray());
+ bind_method(Vector2i, maxi, sarray("with"), varray());
/* Rect2 */
@@ -1875,7 +1889,9 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3, is_finite, sarray(), varray());
bind_method(Vector3, inverse, sarray(), varray());
bind_method(Vector3, clamp, sarray("min", "max"), varray());
+ bind_method(Vector3, clampf, sarray("min", "max"), varray());
bind_method(Vector3, snapped, sarray("step"), varray());
+ bind_method(Vector3, snappedf, sarray("step"), varray());
bind_method(Vector3, rotated, sarray("axis", "angle"), varray());
bind_method(Vector3, lerp, sarray("to", "weight"), varray());
bind_method(Vector3, slerp, sarray("to", "weight"), varray());
@@ -1896,9 +1912,13 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3, project, sarray("b"), varray());
bind_method(Vector3, slide, sarray("n"), varray());
bind_method(Vector3, bounce, sarray("n"), varray());
- bind_method(Vector3, reflect, sarray("direction"), varray());
+ bind_method(Vector3, reflect, sarray("n"), varray());
bind_method(Vector3, sign, sarray(), varray());
bind_method(Vector3, octahedron_encode, sarray(), varray());
+ bind_method(Vector3, min, sarray("with"), varray());
+ bind_method(Vector3, minf, sarray("with"), varray());
+ bind_method(Vector3, max, sarray("with"), varray());
+ bind_method(Vector3, maxf, sarray("with"), varray());
bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray());
/* Vector3i */
@@ -1912,7 +1932,13 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3i, sign, sarray(), varray());
bind_method(Vector3i, abs, sarray(), varray());
bind_method(Vector3i, clamp, sarray("min", "max"), varray());
+ bind_method(Vector3i, clampi, sarray("min", "max"), varray());
bind_method(Vector3i, snapped, sarray("step"), varray());
+ bind_method(Vector3i, snappedi, sarray("step"), varray());
+ bind_method(Vector3i, min, sarray("with"), varray());
+ bind_method(Vector3i, mini, sarray("with"), varray());
+ bind_method(Vector3i, max, sarray("with"), varray());
+ bind_method(Vector3i, maxi, sarray("with"), varray());
/* Vector4 */
@@ -1931,7 +1957,9 @@ static void _register_variant_builtin_methods() {
bind_method(Vector4, posmod, sarray("mod"), varray());
bind_method(Vector4, posmodv, sarray("modv"), varray());
bind_method(Vector4, snapped, sarray("step"), varray());
+ bind_method(Vector4, snappedf, sarray("step"), varray());
bind_method(Vector4, clamp, sarray("min", "max"), varray());
+ bind_method(Vector4, clampf, sarray("min", "max"), varray());
bind_method(Vector4, normalized, sarray(), varray());
bind_method(Vector4, is_normalized, sarray(), varray());
bind_method(Vector4, direction_to, sarray("to"), varray());
@@ -1942,6 +1970,10 @@ static void _register_variant_builtin_methods() {
bind_method(Vector4, is_equal_approx, sarray("to"), varray());
bind_method(Vector4, is_zero_approx, sarray(), varray());
bind_method(Vector4, is_finite, sarray(), varray());
+ bind_method(Vector4, min, sarray("with"), varray());
+ bind_method(Vector4, minf, sarray("with"), varray());
+ bind_method(Vector4, max, sarray("with"), varray());
+ bind_method(Vector4, maxf, sarray("with"), varray());
/* Vector4i */
@@ -1952,7 +1984,13 @@ static void _register_variant_builtin_methods() {
bind_method(Vector4i, sign, sarray(), varray());
bind_method(Vector4i, abs, sarray(), varray());
bind_method(Vector4i, clamp, sarray("min", "max"), varray());
+ bind_method(Vector4i, clampi, sarray("min", "max"), varray());
bind_method(Vector4i, snapped, sarray("step"), varray());
+ bind_method(Vector4i, snappedi, sarray("step"), varray());
+ bind_method(Vector4i, min, sarray("with"), varray());
+ bind_method(Vector4i, mini, sarray("with"), varray());
+ bind_method(Vector4i, max, sarray("with"), varray());
+ bind_method(Vector4i, maxi, sarray("with"), varray());
bind_method(Vector4i, distance_to, sarray("to"), varray());
bind_method(Vector4i, distance_squared_to, sarray("to"), varray());
@@ -2023,7 +2061,9 @@ static void _register_variant_builtin_methods() {
bind_static_method(Color, from_ok_hsl, sarray("h", "s", "l", "alpha"), varray(1.0));
bind_static_method(Color, from_rgbe9995, sarray("rgbe"), varray());
+}
+static void _register_variant_builtin_methods_misc() {
/* RID */
bind_method(RID, is_valid, sarray(), varray());
@@ -2225,7 +2265,10 @@ static void _register_variant_builtin_methods() {
bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, make_read_only, sarray(), varray());
bind_method(Dictionary, is_read_only, sarray(), varray());
+ bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());
+}
+static void _register_variant_builtin_methods_array() {
/* Array */
bind_method(Array, size, sarray(), varray());
@@ -2532,6 +2575,32 @@ static void _register_variant_builtin_methods() {
bind_method(PackedColorArray, rfind, sarray("value", "from"), varray(-1));
bind_method(PackedColorArray, count, sarray("value"), varray());
+ /* Vector4 Array */
+
+ bind_method(PackedVector4Array, size, sarray(), varray());
+ bind_method(PackedVector4Array, is_empty, sarray(), varray());
+ bind_method(PackedVector4Array, set, sarray("index", "value"), varray());
+ bind_method(PackedVector4Array, push_back, sarray("value"), varray());
+ bind_method(PackedVector4Array, append, sarray("value"), varray());
+ bind_method(PackedVector4Array, append_array, sarray("array"), varray());
+ bind_method(PackedVector4Array, remove_at, sarray("index"), varray());
+ bind_method(PackedVector4Array, insert, sarray("at_index", "value"), varray());
+ bind_method(PackedVector4Array, fill, sarray("value"), varray());
+ bind_methodv(PackedVector4Array, resize, &PackedVector4Array::resize_zeroed, sarray("new_size"), varray());
+ bind_method(PackedVector4Array, clear, sarray(), varray());
+ bind_method(PackedVector4Array, has, sarray("value"), varray());
+ bind_method(PackedVector4Array, reverse, sarray(), varray());
+ bind_method(PackedVector4Array, slice, sarray("begin", "end"), varray(INT_MAX));
+ bind_method(PackedVector4Array, to_byte_array, sarray(), varray());
+ bind_method(PackedVector4Array, sort, sarray(), varray());
+ bind_method(PackedVector4Array, bsearch, sarray("value", "before"), varray(true));
+ bind_method(PackedVector4Array, duplicate, sarray(), varray());
+ bind_method(PackedVector4Array, find, sarray("value", "from"), varray(0));
+ bind_method(PackedVector4Array, rfind, sarray("value", "from"), varray(-1));
+ bind_method(PackedVector4Array, count, sarray("value"), varray());
+}
+
+static void _register_variant_builtin_constants() {
/* Register constants */
int ncc = Color::get_named_color_count();
@@ -2689,7 +2758,11 @@ static void _register_variant_builtin_methods() {
}
void Variant::_register_variant_methods() {
- _register_variant_builtin_methods(); //needs to be out due to namespace
+ _register_variant_builtin_methods_string();
+ _register_variant_builtin_methods_math();
+ _register_variant_builtin_methods_misc();
+ _register_variant_builtin_methods_array();
+ _register_variant_builtin_constants();
}
void Variant::_unregister_variant_methods() {
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index b0ed49be5d..1edae407c2 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -211,6 +211,7 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructorToArray<PackedVector2Array>>(sarray("from"));
add_constructor<VariantConstructorToArray<PackedVector3Array>>(sarray("from"));
add_constructor<VariantConstructorToArray<PackedColorArray>>(sarray("from"));
+ add_constructor<VariantConstructorToArray<PackedVector4Array>>(sarray("from"));
add_constructor<VariantConstructNoArgs<PackedByteArray>>(sarray());
add_constructor<VariantConstructor<PackedByteArray, PackedByteArray>>(sarray("from"));
@@ -247,6 +248,10 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<PackedColorArray>>(sarray());
add_constructor<VariantConstructor<PackedColorArray, PackedColorArray>>(sarray("from"));
add_constructor<VariantConstructorFromArray<PackedColorArray>>(sarray("from"));
+
+ add_constructor<VariantConstructNoArgs<PackedVector4Array>>(sarray());
+ add_constructor<VariantConstructor<PackedVector4Array, PackedVector4Array>>(sarray("from"));
+ add_constructor<VariantConstructorFromArray<PackedVector4Array>>(sarray("from"));
}
void Variant::_unregister_variant_constructors() {
diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h
index a93723a910..5afdb884f6 100644
--- a/core/variant/variant_construct.h
+++ b/core/variant/variant_construct.h
@@ -33,7 +33,6 @@
#include "variant.h"
-#include "core/core_string_names.h"
#include "core/crypto/crypto_core.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/compression.h"
@@ -97,6 +96,7 @@ MAKE_PTRCONSTRUCT(PackedStringArray);
MAKE_PTRCONSTRUCT(PackedVector2Array);
MAKE_PTRCONSTRUCT(PackedVector3Array);
MAKE_PTRCONSTRUCT(PackedColorArray);
+MAKE_PTRCONSTRUCT(PackedVector4Array);
MAKE_PTRCONSTRUCT(Variant);
template <typename T, typename... P>
@@ -153,11 +153,14 @@ public:
class VariantConstructorObject {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
- VariantInternal::clear(&r_ret);
if (p_args[0]->get_type() == Variant::NIL) {
+ VariantInternal::clear(&r_ret);
+ VariantTypeChanger<Object *>::change(&r_ret);
VariantInternal::object_assign_null(&r_ret);
r_error.error = Callable::CallError::CALL_OK;
} else if (p_args[0]->get_type() == Variant::OBJECT) {
+ VariantInternal::clear(&r_ret);
+ VariantTypeChanger<Object *>::change(&r_ret);
VariantInternal::object_assign(&r_ret, p_args[0]);
r_error.error = Callable::CallError::CALL_OK;
} else {
@@ -169,6 +172,7 @@ public:
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
VariantInternal::clear(r_ret);
+ VariantTypeChanger<Object *>::change(r_ret);
VariantInternal::object_assign(r_ret, p_args[0]);
}
static void ptr_construct(void *base, const void **p_args) {
@@ -198,11 +202,13 @@ public:
}
VariantInternal::clear(&r_ret);
+ VariantTypeChanger<Object *>::change(&r_ret);
VariantInternal::object_assign_null(&r_ret);
}
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
VariantInternal::clear(r_ret);
+ VariantTypeChanger<Object *>::change(r_ret);
VariantInternal::object_assign_null(r_ret);
}
static void ptr_construct(void *base, const void **p_args) {
diff --git a/core/variant/variant_destruct.cpp b/core/variant/variant_destruct.cpp
index c7455d5117..409f4bd07b 100644
--- a/core/variant/variant_destruct.cpp
+++ b/core/variant/variant_destruct.cpp
@@ -56,6 +56,7 @@ void Variant::_register_variant_destructors() {
add_destructor<VariantDestruct<PackedVector2Array>>();
add_destructor<VariantDestruct<PackedVector3Array>>();
add_destructor<VariantDestruct<PackedColorArray>>();
+ add_destructor<VariantDestruct<PackedVector4Array>>();
}
void Variant::_unregister_variant_destructors() {
diff --git a/core/variant/variant_destruct.h b/core/variant/variant_destruct.h
index c496189c6d..d91d99b02e 100644
--- a/core/variant/variant_destruct.h
+++ b/core/variant/variant_destruct.h
@@ -65,6 +65,7 @@ MAKE_PTRDESTRUCT(PackedStringArray);
MAKE_PTRDESTRUCT(PackedVector2Array);
MAKE_PTRDESTRUCT(PackedVector3Array);
MAKE_PTRDESTRUCT(PackedColorArray);
+MAKE_PTRDESTRUCT(PackedVector4Array);
#undef MAKE_PTRDESTRUCT
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index dbd4a6a7ad..c52ab6917b 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -114,6 +114,9 @@ public:
case Variant::PACKED_COLOR_ARRAY:
init_color_array(v);
break;
+ case Variant::PACKED_VECTOR4_ARRAY:
+ init_vector4_array(v);
+ break;
case Variant::OBJECT:
init_object(v);
break;
@@ -205,6 +208,8 @@ public:
_FORCE_INLINE_ static const PackedVector3Array *get_vector3_array(const Variant *v) { return &static_cast<const Variant::PackedArrayRef<Vector3> *>(v->_data.packed_array)->array; }
_FORCE_INLINE_ static PackedColorArray *get_color_array(Variant *v) { return &static_cast<Variant::PackedArrayRef<Color> *>(v->_data.packed_array)->array; }
_FORCE_INLINE_ static const PackedColorArray *get_color_array(const Variant *v) { return &static_cast<const Variant::PackedArrayRef<Color> *>(v->_data.packed_array)->array; }
+ _FORCE_INLINE_ static PackedVector4Array *get_vector4_array(Variant *v) { return &static_cast<Variant::PackedArrayRef<Vector4> *>(v->_data.packed_array)->array; }
+ _FORCE_INLINE_ static const PackedVector4Array *get_vector4_array(const Variant *v) { return &static_cast<const Variant::PackedArrayRef<Vector4> *>(v->_data.packed_array)->array; }
_FORCE_INLINE_ static Object **get_object(Variant *v) { return (Object **)&v->_get_obj().obj; }
_FORCE_INLINE_ static const Object **get_object(const Variant *v) { return (const Object **)&v->_get_obj().obj; }
@@ -313,6 +318,10 @@ public:
v->_data.packed_array = Variant::PackedArrayRef<Color>::create(Vector<Color>());
v->type = Variant::PACKED_COLOR_ARRAY;
}
+ _FORCE_INLINE_ static void init_vector4_array(Variant *v) {
+ v->_data.packed_array = Variant::PackedArrayRef<Vector4>::create(Vector<Vector4>());
+ v->type = Variant::PACKED_VECTOR4_ARRAY;
+ }
_FORCE_INLINE_ static void init_object(Variant *v) {
object_assign_null(v);
v->type = Variant::OBJECT;
@@ -417,6 +426,8 @@ public:
return get_vector3_array(v);
case Variant::PACKED_COLOR_ARRAY:
return get_color_array(v);
+ case Variant::PACKED_VECTOR4_ARRAY:
+ return get_vector4_array(v);
case Variant::OBJECT:
return get_object(v);
case Variant::VARIANT_MAX:
@@ -501,6 +512,8 @@ public:
return get_vector3_array(v);
case Variant::PACKED_COLOR_ARRAY:
return get_color_array(v);
+ case Variant::PACKED_VECTOR4_ARRAY:
+ return get_vector4_array(v);
case Variant::OBJECT:
return get_object(v);
case Variant::VARIANT_MAX:
@@ -797,6 +810,12 @@ struct VariantGetInternalPtr<PackedColorArray> {
static const PackedColorArray *get_ptr(const Variant *v) { return VariantInternal::get_color_array(v); }
};
+template <>
+struct VariantGetInternalPtr<PackedVector4Array> {
+ static PackedVector4Array *get_ptr(Variant *v) { return VariantInternal::get_vector4_array(v); }
+ static const PackedVector4Array *get_ptr(const Variant *v) { return VariantInternal::get_vector4_array(v); }
+};
+
template <typename T>
struct VariantInternalAccessor {
};
@@ -1058,6 +1077,12 @@ struct VariantInternalAccessor<PackedColorArray> {
};
template <>
+struct VariantInternalAccessor<PackedVector4Array> {
+ static _FORCE_INLINE_ const PackedVector4Array &get(const Variant *v) { return *VariantInternal::get_vector4_array(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const PackedVector4Array &p_value) { *VariantInternal::get_vector4_array(v) = p_value; }
+};
+
+template <>
struct VariantInternalAccessor<Object *> {
static _FORCE_INLINE_ Object *get(const Variant *v) { return const_cast<Object *>(*VariantInternal::get_object(v)); }
static _FORCE_INLINE_ void set(Variant *v, const Object *p_value) { VariantInternal::object_assign(v, p_value); }
@@ -1297,6 +1322,11 @@ struct VariantInitializer<PackedColorArray> {
};
template <>
+struct VariantInitializer<PackedVector4Array> {
+ static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_vector4_array(v); }
+};
+
+template <>
struct VariantInitializer<Object *> {
static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_object(v); }
};
@@ -1490,6 +1520,11 @@ struct VariantDefaultInitializer<PackedColorArray> {
static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_color_array(v) = PackedColorArray(); }
};
+template <>
+struct VariantDefaultInitializer<PackedVector4Array> {
+ static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector4_array(v) = PackedVector4Array(); }
+};
+
template <typename T>
struct VariantTypeChanger {
static _FORCE_INLINE_ void change(Variant *v) {
@@ -1511,7 +1546,7 @@ struct VariantTypeChanger {
template <typename T>
struct VariantTypeAdjust {
_FORCE_INLINE_ static void adjust(Variant *r_ret) {
- VariantTypeChanger<typename GetSimpleTypeT<T>::type_t>::change(r_ret);
+ VariantTypeChanger<GetSimpleTypeT<T>>::change(r_ret);
}
};
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index dcf4b287d1..d2c1cde970 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -274,6 +274,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorAppendArray<Vector2>>(Variant::OP_ADD, Variant::PACKED_VECTOR2_ARRAY, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorAppendArray<Vector3>>(Variant::OP_ADD, Variant::PACKED_VECTOR3_ARRAY, Variant::PACKED_VECTOR3_ARRAY);
register_op<OperatorEvaluatorAppendArray<Color>>(Variant::OP_ADD, Variant::PACKED_COLOR_ARRAY, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorAppendArray<Vector4>>(Variant::OP_ADD, Variant::PACKED_VECTOR4_ARRAY, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorSub<int64_t, int64_t, int64_t>>(Variant::OP_SUBTRACT, Variant::INT, Variant::INT);
register_op<OperatorEvaluatorSub<double, int64_t, double>>(Variant::OP_SUBTRACT, Variant::INT, Variant::FLOAT);
@@ -480,6 +481,7 @@ void Variant::_register_variant_operators() {
register_string_modulo_op(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY);
register_string_modulo_op(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY);
register_string_modulo_op(PackedColorArray, Variant::PACKED_COLOR_ARRAY);
+ register_string_modulo_op(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorPow<int64_t, int64_t, int64_t>>(Variant::OP_POWER, Variant::INT, Variant::INT);
register_op<OperatorEvaluatorPow<double, int64_t, double>>(Variant::OP_POWER, Variant::INT, Variant::FLOAT);
@@ -561,6 +563,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorEqual<PackedVector2Array, PackedVector2Array>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorEqual<PackedVector3Array, PackedVector3Array>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::PACKED_VECTOR3_ARRAY);
register_op<OperatorEvaluatorEqual<PackedColorArray, PackedColorArray>>(Variant::OP_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorEqual<PackedVector4Array, PackedVector4Array>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::BOOL, Variant::NIL>>(Variant::OP_EQUAL, Variant::BOOL, Variant::NIL);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::INT, Variant::NIL>>(Variant::OP_EQUAL, Variant::INT, Variant::NIL);
@@ -598,6 +601,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::NIL>>(Variant::OP_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::NIL>>(Variant::OP_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::BOOL>>(Variant::OP_EQUAL, Variant::NIL, Variant::BOOL);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::INT>>(Variant::OP_EQUAL, Variant::NIL, Variant::INT);
@@ -635,6 +639,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR2_ARRAY>>(Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR3_ARRAY>>(Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR3_ARRAY);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_COLOR_ARRAY>>(Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR4_ARRAY>>(Variant::OP_EQUAL, Variant::NIL, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorAlwaysFalse<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::NIL);
register_op<OperatorEvaluatorNotEqual<bool, bool>>(Variant::OP_NOT_EQUAL, Variant::BOOL, Variant::BOOL);
@@ -680,6 +685,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorNotEqual<PackedVector2Array, PackedVector2Array>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorNotEqual<PackedVector3Array, PackedVector3Array>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::PACKED_VECTOR3_ARRAY);
register_op<OperatorEvaluatorNotEqual<PackedColorArray, PackedColorArray>>(Variant::OP_NOT_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorNotEqual<PackedVector4Array, PackedVector4Array>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::BOOL, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::BOOL, Variant::NIL);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::INT, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::INT, Variant::NIL);
@@ -717,6 +723,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::PACKED_COLOR_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::NIL>>(Variant::OP_NOT_EQUAL, Variant::PACKED_VECTOR4_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::BOOL>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::BOOL);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::INT>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::INT);
@@ -754,6 +761,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR2_ARRAY>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR3_ARRAY>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR3_ARRAY);
register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_COLOR_ARRAY>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorAlwaysTrue<Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR4_ARRAY>>(Variant::OP_NOT_EQUAL, Variant::NIL, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorLess<bool, bool>>(Variant::OP_LESS, Variant::BOOL, Variant::BOOL);
register_op<OperatorEvaluatorLess<int64_t, int64_t>>(Variant::OP_LESS, Variant::INT, Variant::INT);
@@ -944,6 +952,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorNot<PackedVector2Array>>(Variant::OP_NOT, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorNot<PackedVector3Array>>(Variant::OP_NOT, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL);
register_op<OperatorEvaluatorNot<PackedColorArray>>(Variant::OP_NOT, Variant::PACKED_COLOR_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedVector4Array>>(Variant::OP_NOT, Variant::PACKED_VECTOR4_ARRAY, Variant::NIL);
register_string_op(OperatorEvaluatorInStringFind, Variant::OP_IN);
@@ -986,6 +995,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInDictionaryHas<PackedVector2Array>>(Variant::OP_IN, Variant::PACKED_VECTOR2_ARRAY, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<PackedVector3Array>>(Variant::OP_IN, Variant::PACKED_VECTOR3_ARRAY, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<PackedColorArray>>(Variant::OP_IN, Variant::PACKED_COLOR_ARRAY, Variant::DICTIONARY);
+ register_op<OperatorEvaluatorInDictionaryHas<PackedVector4Array>>(Variant::OP_IN, Variant::PACKED_VECTOR4_ARRAY, Variant::DICTIONARY);
register_op<OperatorEvaluatorInArrayFindNil>(Variant::OP_IN, Variant::NIL, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<bool, Array>>(Variant::OP_IN, Variant::BOOL, Variant::ARRAY);
@@ -1026,6 +1036,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInArrayFind<PackedVector2Array, Array>>(Variant::OP_IN, Variant::PACKED_VECTOR2_ARRAY, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<PackedVector3Array, Array>>(Variant::OP_IN, Variant::PACKED_VECTOR3_ARRAY, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<PackedColorArray, Array>>(Variant::OP_IN, Variant::PACKED_COLOR_ARRAY, Variant::ARRAY);
+ register_op<OperatorEvaluatorInArrayFind<PackedVector4Array, Array>>(Variant::OP_IN, Variant::PACKED_VECTOR4_ARRAY, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<int, PackedByteArray>>(Variant::OP_IN, Variant::INT, Variant::PACKED_BYTE_ARRAY);
register_op<OperatorEvaluatorInArrayFind<float, PackedByteArray>>(Variant::OP_IN, Variant::FLOAT, Variant::PACKED_BYTE_ARRAY);
@@ -1047,8 +1058,8 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInArrayFind<Vector2, PackedVector2Array>>(Variant::OP_IN, Variant::VECTOR2, Variant::PACKED_VECTOR2_ARRAY);
register_op<OperatorEvaluatorInArrayFind<Vector3, PackedVector3Array>>(Variant::OP_IN, Variant::VECTOR3, Variant::PACKED_VECTOR3_ARRAY);
-
register_op<OperatorEvaluatorInArrayFind<Color, PackedColorArray>>(Variant::OP_IN, Variant::COLOR, Variant::PACKED_COLOR_ARRAY);
+ register_op<OperatorEvaluatorInArrayFind<Vector4, PackedVector4Array>>(Variant::OP_IN, Variant::VECTOR4, Variant::PACKED_VECTOR4_ARRAY);
register_op<OperatorEvaluatorObjectHasPropertyString>(Variant::OP_IN, Variant::STRING, Variant::OBJECT);
register_op<OperatorEvaluatorObjectHasPropertyStringName>(Variant::OP_IN, Variant::STRING_NAME, Variant::OBJECT);
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index 3142f49fc6..ac39a4135f 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -33,7 +33,6 @@
#include "variant.h"
-#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/object/class_db.h"
@@ -549,14 +548,14 @@ public:
class OperatorEvaluatorEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const ObjectID &a = VariantInternal::get_object_id(&p_left);
- const ObjectID &b = VariantInternal::get_object_id(&p_right);
+ const Object *a = p_left.get_validated_object();
+ const Object *b = p_right.get_validated_object();
*r_ret = a == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const ObjectID &a = VariantInternal::get_object_id(left);
- const ObjectID &b = VariantInternal::get_object_id(right);
+ const Object *a = left->get_validated_object();
+ const Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -568,12 +567,12 @@ public:
class OperatorEvaluatorEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *a = p_left.operator Object *();
+ const Object *a = p_left.get_validated_object();
*r_ret = a == nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *a = left->operator Object *();
+ const Object *a = left->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -585,12 +584,12 @@ public:
class OperatorEvaluatorEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *b = p_right.operator Object *();
+ const Object *b = p_right.get_validated_object();
*r_ret = nullptr == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *b = right->operator Object *();
+ const Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -620,14 +619,14 @@ public:
class OperatorEvaluatorNotEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const ObjectID &a = VariantInternal::get_object_id(&p_left);
- const ObjectID &b = VariantInternal::get_object_id(&p_right);
+ Object *a = p_left.get_validated_object();
+ Object *b = p_right.get_validated_object();
*r_ret = a != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const ObjectID &a = VariantInternal::get_object_id(left);
- const ObjectID &b = VariantInternal::get_object_id(right);
+ Object *a = left->get_validated_object();
+ Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -639,12 +638,12 @@ public:
class OperatorEvaluatorNotEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *a = p_left.operator Object *();
+ Object *a = p_left.get_validated_object();
*r_ret = a != nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *a = left->operator Object *();
+ Object *a = left->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -656,12 +655,12 @@ public:
class OperatorEvaluatorNotEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *b = p_right.operator Object *();
+ Object *b = p_right.get_validated_object();
*r_ret = nullptr != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *b = right->operator Object *();
+ Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index dcb94b16b1..9a0dd712ed 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -489,11 +489,11 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
r_token.value = num.as_int();
}
return OK;
- } else if (is_ascii_char(cchar) || is_underscore(cchar)) {
+ } else if (is_ascii_alphabet_char(cchar) || is_underscore(cchar)) {
StringBuffer<> id;
bool first = true;
- while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
+ while (is_ascii_alphabet_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
id += cchar;
cchar = p_stream->get_char();
first = false;
@@ -1395,6 +1395,24 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
}
value = arr;
+ } else if (id == "PackedVector4Array" || id == "PoolVector4Array" || id == "Vector4Array") {
+ Vector<real_t> args;
+ Error err = _parse_construct<real_t>(p_stream, args, line, r_err_str);
+ if (err) {
+ return err;
+ }
+
+ Vector<Vector4> arr;
+ {
+ int len = args.size() / 4;
+ arr.resize(len);
+ Vector4 *w = arr.ptrw();
+ for (int i = 0; i < len; i++) {
+ w[i] = Vector4(args[i * 4 + 0], args[i * 4 + 1], args[i * 4 + 2], args[i * 4 + 3]);
+ }
+ }
+
+ value = arr;
} else if (id == "PackedColorArray" || id == "PoolColorArray" || id == "ColorArray") {
Vector<float> args;
Error err = _parse_construct<float>(p_stream, args, line, r_err_str);
@@ -1789,7 +1807,7 @@ static String rtos_fix(double p_value) {
}
}
-Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count) {
+Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count, bool p_compat) {
switch (p_variant.get_type()) {
case Variant::NIL: {
p_store_string_func(p_store_string_ud, "null");
@@ -2009,7 +2027,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
}
p_store_string_func(p_store_string_ud, "\"" + E.name + "\":");
- write(obj->get(E.name), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count);
+ write(obj->get(E.name), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
}
}
@@ -2035,9 +2053,9 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, "{\n");
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count);
+ write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
p_store_string_func(p_store_string_ud, ": ");
- write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count);
+ write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
if (E->next()) {
p_store_string_func(p_store_string_ud, ",\n");
} else {
@@ -2096,7 +2114,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
} else {
p_store_string_func(p_store_string_ud, ", ");
}
- write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count);
+ write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
}
p_store_string_func(p_store_string_ud, "]");
@@ -2110,7 +2128,16 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
case Variant::PACKED_BYTE_ARRAY: {
p_store_string_func(p_store_string_ud, "PackedByteArray(");
Vector<uint8_t> data = p_variant;
- if (data.size() > 0) {
+ if (p_compat) {
+ int len = data.size();
+ const uint8_t *ptr = data.ptr();
+ for (int i = 0; i < len; i++) {
+ if (i > 0) {
+ p_store_string_func(p_store_string_ud, ", ");
+ }
+ p_store_string_func(p_store_string_ud, itos(ptr[i]));
+ }
+ } else if (data.size() > 0) {
p_store_string_func(p_store_string_ud, "\"");
p_store_string_func(p_store_string_ud, CryptoCore::b64_encode_str(data.ptr(), data.size()));
p_store_string_func(p_store_string_ud, "\"");
@@ -2239,6 +2266,21 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, ")");
} break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ p_store_string_func(p_store_string_ud, "PackedVector4Array(");
+ Vector<Vector4> data = p_variant;
+ int len = data.size();
+ const Vector4 *ptr = data.ptr();
+
+ for (int i = 0; i < len; i++) {
+ if (i > 0) {
+ p_store_string_func(p_store_string_ud, ", ");
+ }
+ p_store_string_func(p_store_string_ud, rtos_fix(ptr[i].x) + ", " + rtos_fix(ptr[i].y) + ", " + rtos_fix(ptr[i].z) + ", " + rtos_fix(ptr[i].w));
+ }
+
+ p_store_string_func(p_store_string_ud, ")");
+ } break;
default: {
ERR_PRINT("Unknown variant type");
@@ -2255,8 +2297,8 @@ static Error _write_to_str(void *ud, const String &p_string) {
return OK;
}
-Error VariantWriter::write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud) {
+Error VariantWriter::write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, bool p_compat) {
r_string = String();
- return write(p_variant, _write_to_str, &r_string, p_encode_res_func, p_encode_res_ud);
+ return write(p_variant, _write_to_str, &r_string, p_encode_res_func, p_encode_res_ud, 0, p_compat);
}
diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h
index 2f8974849f..b0ac07170d 100644
--- a/core/variant/variant_parser.h
+++ b/core/variant/variant_parser.h
@@ -161,8 +161,8 @@ public:
typedef Error (*StoreStringFunc)(void *ud, const String &p_string);
typedef String (*EncodeResourceFunc)(void *ud, const Ref<Resource> &p_resource);
- static Error write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count = 0);
- static Error write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func = nullptr, void *p_encode_res_ud = nullptr);
+ static Error write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count = 0, bool p_compat = true);
+ static Error write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func = nullptr, void *p_encode_res_ud = nullptr, bool p_compat = true);
};
#endif // VARIANT_PARSER_H
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 9d5ed22b1a..48176163a1 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -251,15 +251,21 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool
return;
}
} else if (type == Variant::DICTIONARY) {
- Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member);
+ Dictionary &dict = *VariantGetInternalPtr<Dictionary>::get_ptr(this);
+
+ if (dict.is_read_only()) {
+ r_valid = false;
+ return;
+ }
+
+ Variant *v = dict.getptr(p_member);
if (v) {
*v = p_value;
- r_valid = true;
} else {
- VariantGetInternalPtr<Dictionary>::get_ptr(this)->operator[](p_member) = p_value;
- r_valid = true;
+ dict[p_member] = p_value;
}
+ r_valid = true;
} else {
r_valid = false;
}
@@ -850,6 +856,7 @@ INDEXED_SETGET_STRUCT_TYPED(PackedVector2Array, Vector2)
INDEXED_SETGET_STRUCT_TYPED(PackedVector3Array, Vector3)
INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String)
INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
+INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
INDEXED_SETGET_STRUCT_DICT(Dictionary)
@@ -917,6 +924,7 @@ void register_indexed_setters_getters() {
REGISTER_INDEXED_MEMBER(PackedVector3Array);
REGISTER_INDEXED_MEMBER(PackedStringArray);
REGISTER_INDEXED_MEMBER(PackedColorArray);
+ REGISTER_INDEXED_MEMBER(PackedVector4Array);
REGISTER_INDEXED_MEMBER(Array);
REGISTER_INDEXED_MEMBER(Dictionary);
@@ -1376,7 +1384,7 @@ bool Variant::iter_init(Variant &r_iter, bool &valid) const {
ref.push_back(r_iter);
Variant vref = ref;
const Variant *refp[] = { &vref };
- Variant ret = _get_obj().obj->callp(CoreStringNames::get_singleton()->_iter_init, refp, 1, ce);
+ Variant ret = _get_obj().obj->callp(CoreStringName(_iter_init), refp, 1, ce);
if (ref.size() != 1 || ce.error != Callable::CallError::CALL_OK) {
valid = false;
@@ -1492,6 +1500,14 @@ bool Variant::iter_init(Variant &r_iter, bool &valid) const {
return true;
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ const Vector<Vector4> *arr = &PackedArrayRef<Vector4>::get_array(_data.packed_array);
+ if (arr->size() == 0) {
+ return false;
+ }
+ r_iter = 0;
+ return true;
+ } break;
default: {
}
}
@@ -1603,7 +1619,7 @@ bool Variant::iter_next(Variant &r_iter, bool &valid) const {
ref.push_back(r_iter);
Variant vref = ref;
const Variant *refp[] = { &vref };
- Variant ret = _get_obj().obj->callp(CoreStringNames::get_singleton()->_iter_next, refp, 1, ce);
+ Variant ret = _get_obj().obj->callp(CoreStringName(_iter_next), refp, 1, ce);
if (ref.size() != 1 || ce.error != Callable::CallError::CALL_OK) {
valid = false;
@@ -1741,6 +1757,16 @@ bool Variant::iter_next(Variant &r_iter, bool &valid) const {
r_iter = idx;
return true;
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ const Vector<Vector4> *arr = &PackedArrayRef<Vector4>::get_array(_data.packed_array);
+ int idx = r_iter;
+ idx++;
+ if (idx >= arr->size()) {
+ return false;
+ }
+ r_iter = idx;
+ return true;
+ } break;
default: {
}
}
@@ -1785,7 +1811,7 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const {
Callable::CallError ce;
ce.error = Callable::CallError::CALL_OK;
const Variant *refp[] = { &r_iter };
- Variant ret = _get_obj().obj->callp(CoreStringNames::get_singleton()->_iter_get, refp, 1, ce);
+ Variant ret = _get_obj().obj->callp(CoreStringName(_iter_get), refp, 1, ce);
if (ce.error != Callable::CallError::CALL_OK) {
r_valid = false;
@@ -1915,6 +1941,17 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const {
#endif
return arr->get(idx);
} break;
+ case PACKED_VECTOR4_ARRAY: {
+ const Vector<Vector4> *arr = &PackedArrayRef<Vector4>::get_array(_data.packed_array);
+ int idx = r_iter;
+#ifdef DEBUG_ENABLED
+ if (idx < 0 || idx >= arr->size()) {
+ r_valid = false;
+ return Variant();
+ }
+#endif
+ return arr->get(idx);
+ } break;
default: {
}
}
@@ -1962,6 +1999,8 @@ Variant Variant::recursive_duplicate(bool p_deep, int recursion_count) const {
return operator Vector<Vector3>().duplicate();
case PACKED_COLOR_ARRAY:
return operator Vector<Color>().duplicate();
+ case PACKED_VECTOR4_ARRAY:
+ return operator Vector<Vector4>().duplicate();
default:
return *this;
}
diff --git a/core/variant/variant_setget.h b/core/variant/variant_setget.h
index 176967344f..cdacbad373 100644
--- a/core/variant/variant_setget.h
+++ b/core/variant/variant_setget.h
@@ -33,7 +33,6 @@
#include "variant.h"
-#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/object/class_db.h"
#include "core/templates/local_vector.h"
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 916ba7aa2f..7534a154a1 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -30,7 +30,6 @@
#include "variant_utility.h"
-#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "core/object/ref_counted.h"
#include "core/os/os.h"
@@ -917,6 +916,8 @@ Variant VariantUtilityFunctions::type_convert(const Variant &p_variant, const Va
return p_variant.operator PackedVector3Array();
case Variant::Type::PACKED_COLOR_ARRAY:
return p_variant.operator PackedColorArray();
+ case Variant::Type::PACKED_VECTOR4_ARRAY:
+ return p_variant.operator PackedVector4Array();
case Variant::Type::VARIANT_MAX:
ERR_PRINT("Invalid type argument to type_convert(), use the TYPE_* constants. Returning the unconverted Variant.");
}