diff options
Diffstat (limited to 'core')
30 files changed, 815 insertions, 161 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 0b5b5627af..3ffd8ee46b 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -46,14 +46,14 @@ int Engine::get_iterations_per_second() const { return ips; } -void Engine::set_physics_jitter_fix(float p_threshold) { +void Engine::set_physics_jitter_fix(double p_threshold) { if (p_threshold < 0) { p_threshold = 0; } physics_jitter_fix = p_threshold; } -float Engine::get_physics_jitter_fix() const { +double Engine::get_physics_jitter_fix() const { return physics_jitter_fix; } @@ -77,11 +77,11 @@ uint32_t Engine::get_frame_delay() const { return _frame_delay; } -void Engine::set_time_scale(float p_scale) { +void Engine::set_time_scale(double p_scale) { _time_scale = p_scale; } -float Engine::get_time_scale() const { +double Engine::get_time_scale() const { return _time_scale; } diff --git a/core/config/engine.h b/core/config/engine.h index 970cfb03e8..3b3e5825b2 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -51,15 +51,15 @@ private: uint64_t frames_drawn = 0; uint32_t _frame_delay = 0; uint64_t _frame_ticks = 0; - float _process_step = 0; + double _process_step = 0; int ips = 60; - float physics_jitter_fix = 0.5; - float _fps = 1; + double physics_jitter_fix = 0.5; + double _fps = 1; int _target_fps = 0; - float _time_scale = 1.0; + double _time_scale = 1.0; uint64_t _physics_frames = 0; - float _physics_interpolation_fraction = 0.0f; + double _physics_interpolation_fraction = 0.0f; bool abort_on_gpu_errors = false; bool use_validation_layers = false; @@ -81,13 +81,13 @@ public: virtual void set_iterations_per_second(int p_ips); virtual int get_iterations_per_second() const; - void set_physics_jitter_fix(float p_threshold); - float get_physics_jitter_fix() const; + void set_physics_jitter_fix(double p_threshold); + double get_physics_jitter_fix() const; virtual void set_target_fps(int p_fps); virtual int get_target_fps() const; - virtual float get_frames_per_second() const { return _fps; } + virtual double get_frames_per_second() const { return _fps; } uint64_t get_frames_drawn(); @@ -95,11 +95,11 @@ public: uint64_t get_process_frames() const { return _process_frames; } bool is_in_physics_frame() const { return _in_physics; } uint64_t get_frame_ticks() const { return _frame_ticks; } - float get_process_step() const { return _process_step; } - float get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; } + double get_process_step() const { return _process_step; } + double get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; } - void set_time_scale(float p_scale); - float get_time_scale() const; + void set_time_scale(double p_scale); + double get_time_scale() const; void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 9e96d4b079..76c918a92f 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -107,6 +107,10 @@ bool _ResourceLoader::exists(const String &p_path, const String &p_type_hint) { return ResourceLoader::exists(p_path, p_type_hint); } +ResourceUID::ID _ResourceLoader::get_resource_uid(const String &p_path) { + return ResourceLoader::get_resource_uid(p_path); +} + void _ResourceLoader::_bind_methods() { ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads"), &_ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false)); ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &_ResourceLoader::load_threaded_get_status, DEFVAL(Array())); @@ -118,6 +122,7 @@ void _ResourceLoader::_bind_methods() { ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &_ResourceLoader::get_dependencies); ClassDB::bind_method(D_METHOD("has_cached", "path"), &_ResourceLoader::has_cached); ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &_ResourceLoader::exists, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &_ResourceLoader::get_resource_uid); BIND_ENUM_CONSTANT(THREAD_LOAD_INVALID_RESOURCE); BIND_ENUM_CONSTANT(THREAD_LOAD_IN_PROGRESS); @@ -356,7 +361,7 @@ void _OS::print_all_textures_by_size() { ResourceCache::get_cached_resources(&rsrc); for (Ref<Resource> &res : rsrc) { - if (!res->is_class("ImageTexture")) { + if (!res->is_class("Texture")) { continue; } @@ -376,14 +381,30 @@ void _OS::print_all_textures_by_size() { imgs.sort(); - for (_OSCoreBindImg &E : imgs) { - total -= E.vram; + if (imgs.size() == 0) { + print_line("No textures seem used in this project."); + } else { + print_line("Textures currently in use, sorted by VRAM usage:\n" + "Path - VRAM usage (Dimensions)"); + } + + for (const _OSCoreBindImg &img : imgs) { + print_line(vformat("%s - %s %s", + img.path, + String::humanize_size(img.vram), + img.size)); } + + print_line(vformat("Total VRAM usage: %s.", String::humanize_size(total))); } void _OS::print_resources_by_type(const Vector<String> &p_types) { - Map<String, int> type_count; + ERR_FAIL_COND_MSG(p_types.size() == 0, + "At least one type should be provided to print resources by type."); + print_line(vformat("Resources currently in use for the following types: %s", p_types)); + + Map<String, int> type_count; List<Ref<Resource>> resources; ResourceCache::get_cached_resources(&resources); @@ -404,6 +425,18 @@ void _OS::print_resources_by_type(const Vector<String> &p_types) { } type_count[r->get_class()]++; + + print_line(vformat("%s: %s", r->get_class(), r->get_path())); + + List<StringName> metas; + r->get_meta_list(&metas); + for (const StringName &meta : metas) { + print_line(vformat(" %s: %s", meta, r->get_meta(meta))); + } + } + + for (const KeyValue<String, int> &E : type_count) { + print_line(vformat("%s count: %d", E.key, E.value)); } } @@ -1733,7 +1766,36 @@ void _Thread::_start_func(void *ud) { memdelete(tud); Callable::CallError ce; const Variant *arg[1] = { &t->userdata }; - int argc = (int)(arg[0]->get_type() != Variant::NIL); + int argc = 0; + if (arg[0]->get_type() != Variant::NIL) { + // Just pass to the target function whatever came as user data + argc = 1; + } else { + // There are two cases of null user data: + // a) The target function has zero parameters and the caller is just honoring that. + // b) The target function has at least one parameter with no default and the caller is + // leveraging the fact that user data defaults to null in Thread.start(). + // We care about the case of more than one parameter because, even if a thread + // function can have one at most, out mindset here is to do our best with the + // only/first one and let the call handle any other error conditions, like too + // much arguments. + // We must check if we are in case b). + int target_param_count = 0; + int target_default_arg_count = 0; + Ref<Script> script = t->target_instance->get_script(); + if (script.is_valid()) { + MethodInfo mi = script->get_method_info(t->target_method); + target_param_count = mi.arguments.size(); + target_default_arg_count = mi.default_arguments.size(); + } else { + MethodBind *method = ClassDB::get_method(t->target_instance->get_class_name(), t->target_method); + target_param_count = method->get_argument_count(); + target_default_arg_count = method->get_default_argument_count(); + } + if (target_param_count >= 1 && target_default_arg_count < target_param_count) { + argc = 1; + } + } Thread::set_name(t->target_method); @@ -2023,15 +2085,15 @@ int _Engine::get_iterations_per_second() const { return Engine::get_singleton()->get_iterations_per_second(); } -void _Engine::set_physics_jitter_fix(float p_threshold) { +void _Engine::set_physics_jitter_fix(double p_threshold) { Engine::get_singleton()->set_physics_jitter_fix(p_threshold); } -float _Engine::get_physics_jitter_fix() const { +double _Engine::get_physics_jitter_fix() const { return Engine::get_singleton()->get_physics_jitter_fix(); } -float _Engine::get_physics_interpolation_fraction() const { +double _Engine::get_physics_interpolation_fraction() const { return Engine::get_singleton()->get_physics_interpolation_fraction(); } @@ -2043,7 +2105,7 @@ int _Engine::get_target_fps() const { return Engine::get_singleton()->get_target_fps(); } -float _Engine::get_frames_per_second() const { +double _Engine::get_frames_per_second() const { return Engine::get_singleton()->get_frames_per_second(); } @@ -2055,11 +2117,11 @@ uint64_t _Engine::get_process_frames() const { return Engine::get_singleton()->get_process_frames(); } -void _Engine::set_time_scale(float p_scale) { +void _Engine::set_time_scale(double p_scale) { Engine::get_singleton()->set_time_scale(p_scale); } -float _Engine::get_time_scale() { +double _Engine::get_time_scale() { return Engine::get_singleton()->get_time_scale(); } @@ -2280,7 +2342,7 @@ void _EngineDebugger::call_add(void *p_user, const Array &p_data) { ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'add' to callable: " + Variant::get_callable_error_text(add, args, 1, err)); } -void _EngineDebugger::call_tick(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { +void _EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { Callable &tick = ((ProfilerCallable *)p_user)->callable_tick; if (tick.is_null()) { return; diff --git a/core/core_bind.h b/core/core_bind.h index 1574c36d3c..8736e6ffd8 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -75,6 +75,7 @@ public: PackedStringArray get_dependencies(const String &p_path); bool has_cached(const String &p_path); bool exists(const String &p_path, const String &p_type_hint = ""); + ResourceUID::ID get_resource_uid(const String &p_path); _ResourceLoader() { singleton = this; } }; @@ -625,21 +626,21 @@ public: void set_iterations_per_second(int p_ips); int get_iterations_per_second() const; - void set_physics_jitter_fix(float p_threshold); - float get_physics_jitter_fix() const; - float get_physics_interpolation_fraction() const; + void set_physics_jitter_fix(double p_threshold); + double get_physics_jitter_fix() const; + double get_physics_interpolation_fraction() const; void set_target_fps(int p_fps); int get_target_fps() const; - float get_frames_per_second() const; + double get_frames_per_second() const; uint64_t get_physics_frames() const; uint64_t get_process_frames() const; int get_frames_drawn(); - void set_time_scale(float p_scale); - float get_time_scale(); + void set_time_scale(double p_scale); + double get_time_scale(); MainLoop *get_main_loop() const; @@ -711,7 +712,7 @@ public: static void call_toggle(void *p_user, bool p_enable, const Array &p_opts); static void call_add(void *p_user, const Array &p_data); - static void call_tick(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time); + static void call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time); static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); _EngineDebugger() { singleton = this; } diff --git a/core/core_constants.cpp b/core/core_constants.cpp index de15cfd14a..cd8096c610 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -133,6 +133,19 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(VALIGN_CENTER); BIND_CORE_ENUM_CONSTANT(VALIGN_BOTTOM); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TOP_TO); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_CENTER_TO); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_BOTTOM_TO); + + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_TOP); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_CENTER); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_BASELINE); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_BOTTOM); + + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TOP); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_CENTER); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_BOTTOM); + // huge list of keys BIND_CORE_CONSTANT(SPKEY); diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h index 3e8c34d84b..98ad2b98d1 100644 --- a/core/debugger/debugger_marshalls.h +++ b/core/debugger/debugger_marshalls.h @@ -83,14 +83,14 @@ struct DebuggerMarshalls { StringName name; int sig_id = -1; int call_count = 0; - float self_time = 0; - float total_time = 0; + double self_time = 0; + double total_time = 0; }; // Servers profiler struct ServerFunctionInfo { StringName name; - float time = 0; + double time = 0; }; struct ServerInfo { @@ -100,11 +100,11 @@ struct DebuggerMarshalls { struct ServersProfilerFrame { int frame_number = 0; - float frame_time = 0; - float idle_time = 0; - float physics_time = 0; - float physics_frame_time = 0; - float script_time = 0; + double frame_time = 0; + double idle_time = 0; + double physics_time = 0; + double physics_frame_time = 0; + double script_time = 0; List<ServerInfo> servers; Vector<ScriptFunctionInfo> script_functions; diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index e5dba029c9..a522b1310f 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -117,7 +117,7 @@ void EngineDebugger::line_poll() { poll_every++; } -void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, float p_physics_frame_time) { +void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) { frame_time = USEC_TO_SEC(p_frame_ticks); process_time = USEC_TO_SEC(p_process_ticks); physics_time = USEC_TO_SEC(p_physics_ticks); diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h index c6daea6e2f..22c6ef943e 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -44,7 +44,7 @@ class ScriptDebugger; class EngineDebugger { public: typedef void (*ProfilingToggle)(void *p_user, bool p_enable, const Array &p_opts); - typedef void (*ProfilingTick)(void *p_user, float p_frame_time, float p_process_time, float p_physics_time, float p_physics_frame_time); + typedef void (*ProfilingTick)(void *p_user, double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); typedef void (*ProfilingAdd)(void *p_user, const Array &p_arr); typedef Error (*CaptureFunc)(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); @@ -85,10 +85,10 @@ public: }; private: - float frame_time = 0.0; - float process_time = 0.0; - float physics_time = 0.0; - float physics_frame_time = 0.0; + double frame_time = 0.0; + double process_time = 0.0; + double physics_time = 0.0; + double physics_frame_time = 0.0; uint32_t poll_every = 0; @@ -120,7 +120,7 @@ public: static void register_uri_handler(const String &p_protocol, CreatePeerFunc p_func); - void iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, float p_physics_frame_time); + void iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time); void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array()); Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured); diff --git a/core/debugger/local_debugger.cpp b/core/debugger/local_debugger.cpp index 24833711d5..b0b3f11424 100644 --- a/core/debugger/local_debugger.cpp +++ b/core/debugger/local_debugger.cpp @@ -41,7 +41,7 @@ struct LocalDebugger::ScriptsProfiler { } }; - float frame_time = 0; + double frame_time = 0; uint64_t idle_accum = 0; Vector<ScriptLanguage::ProfilingInfo> pinfo; @@ -61,7 +61,7 @@ struct LocalDebugger::ScriptsProfiler { } } - void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { frame_time = p_frame_time; _print_frame_data(false); } @@ -92,8 +92,8 @@ struct LocalDebugger::ScriptsProfiler { for (int i = 0; i < ofs; i++) { script_time_us += pinfo[i].self_time; } - float script_time = USEC_TO_SEC(script_time_us); - float total_time = p_accumulated ? script_time : frame_time; + double script_time = USEC_TO_SEC(script_time_us); + double total_time = p_accumulated ? script_time : frame_time; if (!p_accumulated) { print_line("FRAME: total: " + rtos(total_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / total_time) + " %"); @@ -103,8 +103,8 @@ struct LocalDebugger::ScriptsProfiler { for (int i = 0; i < ofs; i++) { print_line(itos(i) + ":" + pinfo[i].signature); - float tt = USEC_TO_SEC(pinfo[i].total_time); - float st = USEC_TO_SEC(pinfo[i].self_time); + double tt = USEC_TO_SEC(pinfo[i].total_time); + double st = USEC_TO_SEC(pinfo[i].self_time); print_line("\ttotal: " + rtos(tt) + "/" + itos(tt * 100 / total_time) + " % \tself: " + rtos(st) + "/" + itos(st * 100 / total_time) + " % tcalls: " + itos(pinfo[i].call_count)); } } @@ -373,7 +373,7 @@ LocalDebugger::LocalDebugger() { ((ScriptsProfiler *)p_user)->toggle(p_enable, p_opts); }, nullptr, - [](void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + [](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { ((ScriptsProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time); }); register_profiler("scripts", scr_prof); diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index 0add12ff3d..f865dfe102 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -50,7 +50,7 @@ void RemoteDebugger::_bind_profiler(const String &p_name, T *p_prof) { [](void *p_user, const Array &p_data) { ((T *)p_user)->add(p_data); }, - [](void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + [](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { ((T *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time); }); EngineDebugger::register_profiler(p_name, prof); @@ -164,7 +164,7 @@ public: } } - void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { uint64_t pt = OS::get_singleton()->get_ticks_msec(); if (pt - last_bandwidth_time > 200) { last_bandwidth_time = pt; @@ -278,10 +278,10 @@ struct RemoteDebugger::ServersProfiler { Map<StringName, ServerInfo> server_data; ScriptsProfiler scripts_profiler; - float frame_time = 0; - float idle_time = 0; - float physics_time = 0; - float physics_frame_time = 0; + double frame_time = 0; + double idle_time = 0; + double physics_time = 0; + double physics_frame_time = 0; void toggle(bool p_enable, const Array &p_opts) { skip_profile_frame = false; @@ -308,7 +308,7 @@ struct RemoteDebugger::ServersProfiler { srv.functions.push_back(fi); } - void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { frame_time = p_frame_time; idle_time = p_idle_time; physics_time = p_physics_time; @@ -358,7 +358,7 @@ struct RemoteDebugger::VisualProfiler { void add(const Array &p_data) {} - void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile(); DebuggerMarshalls::VisualProfilerFrame frame; if (!profile_areas.size()) { @@ -378,7 +378,7 @@ struct RemoteDebugger::PerformanceProfiler { void toggle(bool p_enable, const Array &p_opts) {} void add(const Array &p_data) {} - void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { if (!performance) { return; } @@ -706,6 +706,8 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { Array msg; msg.push_back(p_can_continue); msg.push_back(error_str); + ERR_FAIL_COND(!script_lang); + msg.push_back(script_lang->debug_get_stack_level_count() > 0); send_message("debug_enter", msg); servers_profiler->skip_profile_frame = true; // Avoid frame time spike in debug. @@ -754,7 +756,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { break; } else if (command == "get_stack_dump") { - ERR_FAIL_COND(!script_lang); DebuggerMarshalls::ScriptStackDump dump; int slc = script_lang->debug_get_stack_level_count(); for (int i = 0; i < slc; i++) { @@ -790,7 +791,9 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { script_lang->debug_get_globals(&globals, &globals_vals); ERR_FAIL_COND(globals.size() != globals_vals.size()); - send_message("stack_frame_vars", Array()); + Array var_size; + var_size.push_back(local_vals.size() + member_vals.size() + globals_vals.size()); + send_message("stack_frame_vars", var_size); _send_stack_vars(locals, local_vals, 0); _send_stack_vars(members, member_vals, 1); _send_stack_vars(globals, globals_vals, 2); diff --git a/core/doc_data.h b/core/doc_data.h index 46ab697768..a3011fe275 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -116,6 +116,17 @@ public: } }; + struct ThemeItemDoc { + String name; + String type; + String data_type; + String description; + String default_value; + bool operator<(const ThemeItemDoc &p_theme_item) const { + return name < p_theme_item.name; + } + }; + struct TutorialDoc { String link; String title; @@ -133,7 +144,7 @@ public: Vector<ConstantDoc> constants; Map<String, String> enums; Vector<PropertyDoc> properties; - Vector<PropertyDoc> theme_properties; + Vector<ThemeItemDoc> theme_properties; bool is_script_doc = false; String script_path; bool operator<(const ClassDoc &p_class) const { diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 6714705bb5..15be0f1e36 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -196,7 +196,7 @@ Array InputMap::_action_get_events(const StringName &p_action) { const List<Ref<InputEvent>> *al = action_get_events(p_action); if (al) { for (const List<Ref<InputEvent>>::Element *E = al->front(); E; E = E->next()) { - ret.push_back(E); + ret.push_back(E->get()); } } diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index d21c0bd9a2..e6e79dff8a 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -316,52 +316,53 @@ String FileAccess::get_line() const { } Vector<String> FileAccess::get_csv_line(const String &p_delim) const { - ERR_FAIL_COND_V(p_delim.length() != 1, Vector<String>()); + ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines."); + ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines."); - String l; + String line; + + // CSV can support entries with line breaks as long as they are enclosed + // in double quotes. So our "line" might be more than a single line in the + // text file. int qc = 0; do { if (eof_reached()) { break; } - - l += get_line() + "\n"; + line += get_line() + "\n"; qc = 0; - for (int i = 0; i < l.length(); i++) { - if (l[i] == '"') { + for (int i = 0; i < line.length(); i++) { + if (line[i] == '"') { qc++; } } - } while (qc % 2); - l = l.substr(0, l.length() - 1); + // Remove the extraneous newline we've added above. + line = line.substr(0, line.length() - 1); Vector<String> strings; bool in_quote = false; String current; - for (int i = 0; i < l.length(); i++) { - char32_t c = l[i]; - char32_t s[2] = { 0, 0 }; - + for (int i = 0; i < line.length(); i++) { + char32_t c = line[i]; + // A delimiter ends the current entry, unless it's in a quoted string. if (!in_quote && c == p_delim[0]) { strings.push_back(current); current = String(); } else if (c == '"') { - if (l[i + 1] == '"' && in_quote) { - s[0] = '"'; - current += s; + // Doubled quotes are escapes for intentional quotes in the string. + if (line[i + 1] == '"' && in_quote) { + current += '"'; i++; } else { in_quote = !in_quote; } } else { - s[0] = c; - current += s; + current += c; } } - strings.push_back(current); return strings; diff --git a/core/io/ip.cpp b/core/io/ip.cpp index cd1b6d1994..e3102508a3 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -83,8 +83,23 @@ struct _IP_ResolverPrivate { continue; } - IP::get_singleton()->_resolve_hostname(queue[i].response, queue[i].hostname, queue[i].type); - queue[i].status.set(queue[i].response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); + mutex.lock(); + List<IPAddress> response; + String hostname = queue[i].hostname; + IP::Type type = queue[i].type; + mutex.unlock(); + + // We should not lock while resolving the hostname, + // only when modifying the queue. + IP::get_singleton()->_resolve_hostname(response, hostname, type); + + MutexLock lock(mutex); + // Could have been completed by another function, or deleted. + if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { + continue; + } + queue[i].response = response; + queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); } } @@ -93,8 +108,6 @@ struct _IP_ResolverPrivate { while (!ipr->thread_abort) { ipr->sem.wait(); - - MutexLock lock(ipr->mutex); ipr->resolve_queues(); } } @@ -107,17 +120,23 @@ struct _IP_ResolverPrivate { }; IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { - MutexLock lock(resolver->mutex); - List<IPAddress> res; - String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); + + resolver->mutex.lock(); if (resolver->cache.has(key)) { res = resolver->cache[key]; } else { + // This should be run unlocked so the resolver thread can keep + // resolving other requests. + resolver->mutex.unlock(); _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). resolver->cache[key] = res; } + resolver->mutex.unlock(); for (int i = 0; i < res.size(); ++i) { if (res[i].is_valid()) { @@ -128,14 +147,23 @@ IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { } Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) { - MutexLock lock(resolver->mutex); - + List<IPAddress> res; String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - if (!resolver->cache.has(key)) { - _resolve_hostname(resolver->cache[key], p_hostname, p_type); - } - List<IPAddress> res = resolver->cache[key]; + resolver->mutex.lock(); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep resolving + // other requests. + resolver->mutex.unlock(); + _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). + resolver->cache[key] = res; + } + resolver->mutex.unlock(); Array result; for (int i = 0; i < res.size(); ++i) { @@ -178,13 +206,12 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const { ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE); - MutexLock lock(resolver->mutex); - - if (resolver->queue[p_id].status.get() == IP::RESOLVER_STATUS_NONE) { + IP::ResolverStatus res = resolver->queue[p_id].status.get(); + if (res == IP::RESOLVER_STATUS_NONE) { ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE"); return IP::RESOLVER_STATUS_NONE; } - return resolver->queue[p_id].status.get(); + return res; } IPAddress IP::get_resolve_item_address(ResolverID p_id) const { @@ -230,8 +257,6 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const { void IP::erase_resolve_item(ResolverID p_id) { ERR_FAIL_INDEX(p_id, IP::RESOLVER_MAX_QUERIES); - MutexLock lock(resolver->mutex); - resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE); } diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index 4f85eced93..e7d5b78d14 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -746,7 +746,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_INT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -795,7 +795,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_FLOAT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -804,7 +804,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int Vector<double> data; if (count) { - //const double*rbuf=(const double*)buf; data.resize(count); double *w = data.ptrw(); for (int64_t i = 0; i < count; i++) { @@ -1519,7 +1518,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo int datasize = sizeof(int64_t); if (buf) { - encode_uint64(datalen, buf); + encode_uint32(datalen, buf); buf += 4; const int64_t *r = data.ptr(); for (int64_t i = 0; i < datalen; i++) { diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index d4f09b2135..f792dc6cb4 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -33,6 +33,7 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" #include <stdint.h> @@ -146,6 +147,7 @@ void MultiplayerAPI::poll() { } void MultiplayerAPI::clear() { + replicated_nodes.clear(); connected_peers.clear(); path_get_cache.clear(); path_send_cache.clear(); @@ -322,6 +324,12 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ case NETWORK_COMMAND_RAW: { _process_raw(p_from, p_packet, p_packet_len); } break; + case NETWORK_COMMAND_SPAWN: { + _process_spawn_despawn(p_from, p_packet, p_packet_len, true); + } break; + case NETWORK_COMMAND_DESPAWN: { + _process_spawn_despawn(p_from, p_packet, p_packet_len, false); + } break; } } @@ -478,6 +486,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, packet.write[1] = valid_rpc_checksum; encode_cstring(pname.get_data(), &packet.write[2]); + network_peer->set_transfer_channel(0); network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); network_peer->set_target_peer(p_from); network_peer->put_packet(packet.ptr(), packet.size()); @@ -505,6 +514,64 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, E->get() = true; } +void MultiplayerAPI::_process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { + ERR_FAIL_COND_MSG(p_packet_len < 18, "Invalid spawn packet received"); + int ofs = 1; + ResourceUID::ID id = decode_uint64(&p_packet[ofs]); + ofs += 8; + ERR_FAIL_COND_MSG(!spawnables.has(id), "Invalid spawn ID received " + itos(id)); + + uint32_t node_target = decode_uint32(&p_packet[ofs]); + Node *parent = _process_get_node(p_from, nullptr, node_target, 0); + ofs += 4; + ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); + + uint32_t name_len = decode_uint32(&p_packet[ofs]); + ofs += 4; + ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); + ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); + + const String name = String::utf8((const char *)&p_packet[ofs], name_len); + // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. + ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); + ofs += name_len; + PackedByteArray data; + if (p_packet_len > ofs) { + data.resize(p_packet_len - ofs); + memcpy(data.ptrw(), &p_packet[ofs], data.size()); + } + + SpawnMode mode = spawnables[id]; + if (mode == SPAWN_MODE_SERVER && p_from == 1) { + String scene_path = ResourceUID::get_singleton()->get_id_path(id); + if (p_spawn) { + ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); + RES res = ResourceLoader::load(scene_path); + ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); + PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); + ERR_FAIL_COND(!scene); + Node *node = scene->instantiate(); + ERR_FAIL_COND(!node); + replicated_nodes[node->get_instance_id()] = id; + parent->_add_child_nocheck(node, name); + emit_signal(SNAME("network_spawn"), p_from, id, node, data); + } else { + ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); + Node *node = parent->get_node(name); + ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); + emit_signal(SNAME("network_despawn"), p_from, id, node, data); + replicated_nodes.erase(node->get_instance_id()); + node->queue_delete(); + } + } else { + if (p_spawn) { + emit_signal(SNAME("network_spawn_request"), p_from, id, parent, name, data); + } else { + emit_signal(SNAME("network_despawn_request"), p_from, id, parent, name, data); + } + } +} + bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. @@ -557,6 +624,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC for (int &E : peers_to_add) { network_peer->set_target_peer(E); // To all of you. + network_peer->set_transfer_channel(0); network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); network_peer->put_packet(packet.ptr(), packet.size()); @@ -858,6 +926,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const #endif // Take chance and set transfer mode, since all send methods will use it. + network_peer->set_transfer_channel(p_config.channel); network_peer->set_transfer_mode(p_config.transfer_mode); if (has_all_peers) { @@ -906,6 +975,16 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); + if (is_network_server()) { + for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { + // Only server mode adds to replicated_nodes, no need to check it. + Object *obj = ObjectDB::get_instance(E.key); + ERR_CONTINUE(!obj); + Node *node = Object::cast_to<Node>(obj); + ERR_CONTINUE(!node); + _send_spawn_despawn(p_id, E.value, node->get_path(), nullptr, 0, true); + } + } emit_signal(SNAME("network_peer_connected"), p_id); } @@ -921,6 +1000,10 @@ void MultiplayerAPI::_del_peer(int p_id) { PathSentCache *psc = path_send_cache.getptr(E); psc->confirmed_peers.erase(p_id); } + if (p_id == 1) { + // Erase server replicated nodes, but do not queue them for deletion. + replicated_nodes.clear(); + } emit_signal(SNAME("network_peer_disconnected"), p_id); } @@ -996,7 +1079,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); } -Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode) { +Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); @@ -1007,6 +1090,7 @@ Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPe memcpy(&packet_cache.write[1], &r[0], p_data.size()); network_peer->set_target_peer(p_to); + network_peer->set_transfer_channel(p_channel); network_peer->set_transfer_mode(p_mode); return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); @@ -1063,10 +1147,103 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } +Error MultiplayerAPI::spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode) { + ERR_FAIL_COND_V(p_mode < SPAWN_MODE_NONE || p_mode > SPAWN_MODE_CUSTOM, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); +#ifdef TOOLS_ENABLED + String path = ResourceUID::get_singleton()->get_id_path(p_id); + RES res = ResourceLoader::load(path); + ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); +#endif + if (p_mode == SPAWN_MODE_NONE) { + if (spawnables.has(p_id)) { + spawnables.erase(p_id); + } + } else { + spawnables[p_id] = p_mode; + } + return OK; +} + +Error MultiplayerAPI::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), false); +} + +Error MultiplayerAPI::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), true); +} + +Error MultiplayerAPI::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn) { + ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!spawnables.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + + NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); + const Vector<StringName> names = rel_path.get_names(); + ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); + + NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); + ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); + + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(parent); + if (!psc) { + // Path is not cached, create. + path_send_cache[parent] = PathSentCache(); + psc = path_send_cache.getptr(parent); + psc->id = last_send_cache_id++; + } + _send_confirm_path(root_node->get_node(parent), parent, psc, p_peer_id); + + const CharString cname = String(names[names.size() - 1]).utf8(); + int nlen = encode_cstring(cname.get_data(), nullptr); + MAKE_ROOM(1 + 8 + 4 + 4 + nlen + p_data_len); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = p_spawn ? NETWORK_COMMAND_SPAWN : NETWORK_COMMAND_DESPAWN; + int ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ofs += encode_uint32(psc->id, &ptr[ofs]); + ofs += encode_uint32(nlen, &ptr[ofs]); + ofs += encode_cstring(cname.get_data(), &ptr[ofs]); + memcpy(&ptr[ofs], p_data, p_data_len); + network_peer->set_target_peer(p_peer_id); + network_peer->set_transfer_channel(0); + network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + return network_peer->put_packet(ptr, ofs + p_data_len); +} + +void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter) { + if (!has_network_peer()) { + return; + } + ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); + NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); + if (path.is_empty()) { + return; + } + ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); + if (!spawnables.has(id)) { + return; + } + SpawnMode mode = spawnables[id]; + if (p_enter) { + if (mode == SPAWN_MODE_SERVER && is_network_server()) { + replicated_nodes[p_node->get_instance_id()] = id; + _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, true); + } + emit_signal(SNAME("network_spawnable_added"), id, p_node); + } else { + if (mode == SPAWN_MODE_SERVER && is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { + replicated_nodes.erase(p_node->get_instance_id()); + _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, false); + } + emit_signal(SNAME("network_spawnable_removed"), id, p_node); + } +} + void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE)); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); @@ -1081,6 +1258,9 @@ void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("spawnable_config", "scene_id", "spawn_mode"), &MultiplayerAPI::spawnable_config); + ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_despawn, DEFVAL(PackedByteArray())); + ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_spawn, DEFVAL(PackedByteArray())); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); @@ -1094,11 +1274,21 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); + ADD_SIGNAL(MethodInfo("network_despawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_despawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawnable_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("network_spawnable_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); BIND_ENUM_CONSTANT(RPC_MODE_MASTER); BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); + + BIND_ENUM_CONSTANT(SPAWN_MODE_NONE); + BIND_ENUM_CONSTANT(SPAWN_MODE_SERVER); + BIND_ENUM_CONSTANT(SPAWN_MODE_CUSTOM); } MultiplayerAPI::MultiplayerAPI() { diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index cc994a9852..9552a0bf35 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -32,6 +32,7 @@ #define MULTIPLAYER_API_H #include "core/io/multiplayer_peer.h" +#include "core/io/resource_uid.h" #include "core/object/ref_counted.h" class MultiplayerAPI : public RefCounted { @@ -45,6 +46,12 @@ public: RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets }; + enum SpawnMode { + SPAWN_MODE_NONE, + SPAWN_MODE_SERVER, + SPAWN_MODE_CUSTOM, + }; + struct RPCConfig { StringName name; RPCMode rpc_mode = RPC_MODE_DISABLED; @@ -82,10 +89,12 @@ private: }; Ref<MultiplayerPeer> network_peer; + Map<ResourceUID::ID, SpawnMode> spawnables; int rpc_sender_id = 0; Set<int> connected_peers; HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; + Map<ObjectID, ResourceUID::ID> replicated_nodes; int last_send_cache_id; Vector<uint8_t> packet_cache; Node *root_node = nullptr; @@ -97,6 +106,7 @@ protected: void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); @@ -113,6 +123,8 @@ public: NETWORK_COMMAND_SIMPLIFY_PATH, NETWORK_COMMAND_CONFIRM_PATH, NETWORK_COMMAND_RAW, + NETWORK_COMMAND_SPAWN, + NETWORK_COMMAND_DESPAWN, }; enum NetworkNodeIdCompression { @@ -132,7 +144,7 @@ public: Node *get_root_node(); void set_network_peer(const Ref<MultiplayerPeer> &p_peer); Ref<MultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE); + Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); // Called by Node.rpc void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); @@ -154,10 +166,17 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + Error spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode); + Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); + Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); + Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn); + void scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter); + MultiplayerAPI(); ~MultiplayerAPI(); }; VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); +VARIANT_ENUM_CAST(MultiplayerAPI::SpawnMode); #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/io/multiplayer_peer.cpp index 8b3b1eef8e..83cf24d7e3 100644 --- a/core/io/multiplayer_peer.cpp +++ b/core/io/multiplayer_peer.cpp @@ -54,6 +54,8 @@ uint32_t MultiplayerPeer::generate_unique_id() const { } void MultiplayerPeer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel); + ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel); ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode); ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode); ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer); @@ -71,6 +73,7 @@ void MultiplayerPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); diff --git a/core/io/multiplayer_peer.h b/core/io/multiplayer_peer.h index 91a3ad7954..7ca4e7930b 100644 --- a/core/io/multiplayer_peer.h +++ b/core/io/multiplayer_peer.h @@ -56,6 +56,8 @@ public: CONNECTION_CONNECTED, }; + virtual void set_transfer_channel(int p_channel) = 0; + virtual int get_transfer_channel() const = 0; virtual void set_transfer_mode(TransferMode p_mode) = 0; virtual TransferMode get_transfer_mode() const = 0; virtual void set_target_peer(int p_peer_id) = 0; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 727611a573..0262655927 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -552,5 +552,7 @@ void ResourceCache::dump(const char *p_file, bool p_short) { } lock.read_unlock(); +#else + WARN_PRINT("ResourceCache::dump only with in debug builds."); #endif } diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index a3ebc32cc5..00d4d093da 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -903,10 +903,11 @@ void ResourceLoaderBinary::open(FileAccess *p_f, bool p_no_resources, bool p_kee if (using_uids) { uid = f->get_64(); } else { + f->get_64(); // skip over uid field uid = ResourceUID::INVALID_ID; } - for (int i = 0; i < 5; i++) { + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->get_32(); //skip a few reserved fields } @@ -1209,8 +1210,8 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons fw->store_32(flags); fw->store_64(uid_data); - for (int i = 0; i < 5; i++) { - f->store_32(0); // reserved + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { + fw->store_32(0); // reserved f->get_32(); } @@ -1264,7 +1265,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons if (using_uids) { ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(full_path); - f->store_64(uid); + fw->store_64(uid); } } @@ -1902,7 +1903,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS); ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true); f->store_64(uid); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->store_32(0); // reserved } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index ac964d2053..a6e6d1848e 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -164,6 +164,8 @@ public: enum { FORMAT_FLAG_NAMED_SCENE_IDS = 1, FORMAT_FLAG_UIDS = 2, + // Amount of reserved 32-bit fields in resource header + RESERVED_FIELDS = 11 }; Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); diff --git a/core/math/math_defs.h b/core/math/math_defs.h index 7692e1be47..c3a8f910c0 100644 --- a/core/math/math_defs.h +++ b/core/math/math_defs.h @@ -81,6 +81,26 @@ enum VAlign { VALIGN_BOTTOM }; +enum InlineAlign { + // Image alignment points. + INLINE_ALIGN_TOP_TO = 0b0000, + INLINE_ALIGN_CENTER_TO = 0b0001, + INLINE_ALIGN_BOTTOM_TO = 0b0010, + INLINE_ALIGN_IMAGE_MASK = 0b0011, + + // Text alignment points. + INLINE_ALIGN_TO_TOP = 0b0000, + INLINE_ALIGN_TO_CENTER = 0b0100, + INLINE_ALIGN_TO_BASELINE = 0b1000, + INLINE_ALIGN_TO_BOTTOM = 0b1100, + INLINE_ALIGN_TEXT_MASK = 0b1100, + + // Presets. + INLINE_ALIGN_TOP = INLINE_ALIGN_TOP_TO | INLINE_ALIGN_TO_TOP, + INLINE_ALIGN_CENTER = INLINE_ALIGN_CENTER_TO | INLINE_ALIGN_TO_CENTER, + INLINE_ALIGN_BOTTOM = INLINE_ALIGN_BOTTOM_TO | INLINE_ALIGN_TO_BOTTOM +}; + enum Side { SIDE_LEFT, SIDE_TOP, diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index 3d8e70cec7..cadfdc13d1 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -75,16 +75,24 @@ public: bool operator!=(const Transform3D &p_transform) const; _FORCE_INLINE_ Vector3 xform(const Vector3 &p_vector) const; + _FORCE_INLINE_ AABB xform(const AABB &p_aabb) const; + _FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const; + + // NOTE: These are UNSAFE with non-uniform scaling, and will produce incorrect results. + // They use the transpose. + // For safe inverse transforms, xform by the affine_inverse. _FORCE_INLINE_ Vector3 xform_inv(const Vector3 &p_vector) const; + _FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const; + _FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const; + // Safe with non-uniform scaling (uses affine_inverse). _FORCE_INLINE_ Plane xform(const Plane &p_plane) const; _FORCE_INLINE_ Plane xform_inv(const Plane &p_plane) const; - _FORCE_INLINE_ AABB xform(const AABB &p_aabb) const; - _FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const; - - _FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const; - _FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const; + // These fast versions use precomputed affine inverse, and should be used in bottleneck areas where + // multiple planes are to be transformed. + _FORCE_INLINE_ Plane xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const; + static _FORCE_INLINE_ Plane xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose); void operator*=(const Transform3D &p_transform); Transform3D operator*(const Transform3D &p_transform) const; @@ -130,30 +138,20 @@ _FORCE_INLINE_ Vector3 Transform3D::xform_inv(const Vector3 &p_vector) const { (basis.elements[0][2] * v.x) + (basis.elements[1][2] * v.y) + (basis.elements[2][2] * v.z)); } +// Neither the plane regular xform or xform_inv are particularly efficient, +// as they do a basis inverse. For xforming a large number +// of planes it is better to pre-calculate the inverse transpose basis once +// and reuse it for each plane, by using the 'fast' version of the functions. _FORCE_INLINE_ Plane Transform3D::xform(const Plane &p_plane) const { - Vector3 point = p_plane.normal * p_plane.d; - Vector3 point_dir = point + p_plane.normal; - point = xform(point); - point_dir = xform(point_dir); - - Vector3 normal = point_dir - point; - normal.normalize(); - real_t d = normal.dot(point); - - return Plane(normal, d); + Basis b = basis.inverse(); + b.transpose(); + return xform_fast(p_plane, b); } _FORCE_INLINE_ Plane Transform3D::xform_inv(const Plane &p_plane) const { - Vector3 point = p_plane.normal * p_plane.d; - Vector3 point_dir = point + p_plane.normal; - point = xform_inv(point); - point_dir = xform_inv(point_dir); - - Vector3 normal = point_dir - point; - normal.normalize(); - real_t d = normal.dot(point); - - return Plane(normal, d); + Transform3D inv = affine_inverse(); + Basis basis_transpose = basis.transposed(); + return xform_inv_fast(p_plane, inv, basis_transpose); } _FORCE_INLINE_ AABB Transform3D::xform(const AABB &p_aabb) const { @@ -231,4 +229,37 @@ Vector<Vector3> Transform3D::xform_inv(const Vector<Vector3> &p_array) const { return array; } +_FORCE_INLINE_ Plane Transform3D::xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const { + // Transform a single point on the plane. + Vector3 point = p_plane.normal * p_plane.d; + point = xform(point); + + // Use inverse transpose for correct normals with non-uniform scaling. + Vector3 normal = p_basis_inverse_transpose.xform(p_plane.normal); + normal.normalize(); + + real_t d = normal.dot(point); + return Plane(normal, d); +} + +_FORCE_INLINE_ Plane Transform3D::xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose) { + // Transform a single point on the plane. + Vector3 point = p_plane.normal * p_plane.d; + point = p_inverse.xform(point); + + // Note that instead of precalculating the transpose, an alternative + // would be to use the transpose for the basis transform. + // However that would be less SIMD friendly (requiring a swizzle). + // So the cost is one extra precalced value in the calling code. + // This is probably worth it, as this could be used in bottleneck areas. And + // where it is not a bottleneck, the non-fast method is fine. + + // Use transpose for correct normals with non-uniform scaling. + Vector3 normal = p_basis_transpose.xform(p_plane.normal); + normal.normalize(); + + real_t d = normal.dot(point); + return Plane(normal, d); +} + #endif // TRANSFORM_H diff --git a/core/object/script_language.h b/core/object/script_language.h index 2cbaa0f52e..385bf79c1a 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -310,6 +310,7 @@ public: Ref<Script> script; String class_name; String class_member; + String class_path; int location; }; diff --git a/core/os/os.cpp b/core/os/os.cpp index f7af74da3e..76a6da51e1 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -178,7 +178,7 @@ static void _OS_printres(Object *p_obj) { return; } - String str = itos(res->get_instance_id()) + String(res->get_class()) + ":" + String(res->get_name()) + " - " + res->get_path(); + String str = vformat("%s - %s - %s", res->to_string(), res->get_name(), res->get_path()); if (_OSPRF) { _OSPRF->store_line(str); } else { @@ -215,14 +215,6 @@ void OS::dump_resources_to_file(const char *p_file) { ResourceCache::dump(p_file); } -void OS::set_no_window_mode(bool p_enable) { - _no_window = p_enable; -} - -bool OS::is_no_window_mode_enabled() const { - return _no_window; -} - int OS::get_exit_code() const { return _exit_code; } diff --git a/core/os/os.h b/core/os/os.h index fcb195afe1..0466d94acd 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -53,7 +53,6 @@ class OS { bool _verbose_stdout = false; bool _debug_stdout = false; String _local_clipboard; - bool _no_window = false; int _exit_code = EXIT_FAILURE; // unexpected exit is marked as failure int _orientation; bool _allow_hidpi = false; @@ -269,9 +268,6 @@ public: virtual Error move_to_trash(const String &p_path) { return FAILED; } - virtual void set_no_window_mode(bool p_enable); - virtual bool is_no_window_mode_enabled() const; - virtual void debug_break(); virtual int get_exit_code() const; diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 19d23fd375..cb7d924556 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -929,6 +929,66 @@ void Translation::_bind_methods() { /////////////////////////////////////////////// +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"ź" }, +}; + bool TranslationServer::is_locale_valid(const String &p_locale) { const char **ptr = locale_list; @@ -1101,10 +1161,10 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin } if (!res) { - return p_message; + return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message; } - return res; + 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 { @@ -1217,7 +1277,18 @@ void TranslationServer::setup() { } 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 { String options = ""; @@ -1258,10 +1329,10 @@ StringName TranslationServer::tool_translate(const StringName &p_message, const if (tool_translation.is_valid()) { StringName r = tool_translation->get_message(p_message, p_context); if (r) { - return r; + return editor_pseudolocalization ? tool_pseudolocalize(r) : r; } } - return p_message; + 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 { @@ -1306,6 +1377,181 @@ StringName TranslationServer::doc_translate_plural(const StringName &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; + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } + ResourceLoader::reload_translation_remaps(); +} + +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"); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } + ResourceLoader::reload_translation_remaps(); +} + +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.size(); 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.size(); 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.size(); 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.size(); 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(String &p_message, int p_length) const { + String res; + String prefix = pseudolocalization_prefix; + String suffix; + for (int i = 0; i < p_length * expansion_ratio / 2; i++) { + prefix += "_"; + suffix += "_"; + } + suffix += pseudolocalization_suffix; + res += prefix; + res += p_message; + res += suffix; + return res; +} + +const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { + if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) { + 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_message[p_index] == '%' && p_index < p_message.size() - 1 && + (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'); +} + 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); @@ -1322,6 +1568,12 @@ void TranslationServer::_bind_methods() { 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() { diff --git a/core/string/translation.h b/core/string/translation.h index 72a828227e..4f179ac0fe 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -77,6 +77,26 @@ class TranslationServer : public Object { 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(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); @@ -104,6 +124,13 @@ public: 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(); + static Vector<String> get_all_locales(); static Vector<String> get_all_locale_names(); static bool is_locale_valid(const String &p_locale); diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 3cb2a6bfb5..001da3ddcb 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -101,6 +101,7 @@ VARIANT_ENUM_CAST(MouseButton); VARIANT_ENUM_CAST(Orientation); VARIANT_ENUM_CAST(HAlign); VARIANT_ENUM_CAST(VAlign); +VARIANT_ENUM_CAST(InlineAlign); VARIANT_ENUM_CAST(PropertyHint); VARIANT_ENUM_CAST(PropertyUsageFlags); VARIANT_ENUM_CAST(Variant::Type); |