summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/engine.cpp8
-rw-r--r--core/config/engine.h24
-rw-r--r--core/core_bind.cpp86
-rw-r--r--core/core_bind.h15
-rw-r--r--core/core_constants.cpp13
-rw-r--r--core/debugger/debugger_marshalls.h16
-rw-r--r--core/debugger/engine_debugger.cpp2
-rw-r--r--core/debugger/engine_debugger.h12
-rw-r--r--core/debugger/local_debugger.cpp14
-rw-r--r--core/debugger/remote_debugger.cpp25
-rw-r--r--core/doc_data.h13
-rw-r--r--core/input/input_map.cpp2
-rw-r--r--core/io/file_access.cpp37
-rw-r--r--core/io/ip.cpp63
-rw-r--r--core/io/marshalls.cpp7
-rw-r--r--core/io/multiplayer_api.cpp194
-rw-r--r--core/io/multiplayer_api.h21
-rw-r--r--core/io/multiplayer_peer.cpp3
-rw-r--r--core/io/multiplayer_peer.h2
-rw-r--r--core/io/resource.cpp2
-rw-r--r--core/io/resource_format_binary.cpp11
-rw-r--r--core/io/resource_format_binary.h2
-rw-r--r--core/math/math_defs.h20
-rw-r--r--core/math/transform_3d.h81
-rw-r--r--core/object/script_language.h1
-rw-r--r--core/os/os.cpp10
-rw-r--r--core/os/os.h4
-rw-r--r--core/string/translation.cpp260
-rw-r--r--core/string/translation.h27
-rw-r--r--core/variant/binder_common.h1
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);