summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct29
-rw-r--r--core/SCsub1
-rw-r--r--core/config/engine.cpp5
-rw-r--r--core/core_bind.cpp65
-rw-r--r--core/core_bind.h25
-rw-r--r--core/debugger/debugger_marshalls.cpp183
-rw-r--r--core/debugger/debugger_marshalls.h87
-rw-r--r--core/debugger/engine_profiler.cpp82
-rw-r--r--core/debugger/engine_profiler.h65
-rw-r--r--core/debugger/remote_debugger.cpp354
-rw-r--r--core/debugger/remote_debugger.h14
-rw-r--r--core/math/vector2.h4
-rw-r--r--core/math/vector2i.h22
-rw-r--r--core/multiplayer/multiplayer_api.cpp1
-rw-r--r--core/register_core_types.cpp3
-rw-r--r--core/version.h3
-rw-r--r--doc/classes/Color.xml5
-rw-r--r--doc/classes/EngineDebugger.xml9
-rw-r--r--doc/classes/EngineProfiler.xml39
-rw-r--r--doc/classes/Popup.xml8
-rw-r--r--doc/classes/PopupMenu.xml2
-rw-r--r--doc/classes/Tree.xml7
-rw-r--r--doc/classes/TreeItem.xml20
-rw-r--r--doc/classes/Window.xml2
-rw-r--r--drivers/vulkan/vulkan_context.cpp6
-rw-r--r--editor/animation_bezier_editor.cpp956
-rw-r--r--editor/animation_bezier_editor.h44
-rw-r--r--editor/animation_track_editor.cpp73
-rw-r--r--editor/animation_track_editor.h5
-rw-r--r--editor/debugger/editor_network_profiler.cpp22
-rw-r--r--editor/debugger/editor_network_profiler.h6
-rw-r--r--editor/debugger/script_editor_debugger.cpp48
-rw-r--r--editor/editor_about.cpp3
-rw-r--r--editor/editor_node.cpp1
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp113
-rw-r--r--editor/plugins/node_3d_editor_plugin.h79
-rw-r--r--editor/project_manager.cpp1
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--main/main.cpp7
-rw-r--r--methods.py8
-rw-r--r--modules/gltf/gltf_document.cpp5
-rw-r--r--platform/iphone/godot_view.h5
-rw-r--r--platform/iphone/godot_view.mm8
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.mm31
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp7
-rw-r--r--platform/osx/crash_handler_osx.mm7
-rw-r--r--platform/windows/crash_handler_windows.cpp7
-rw-r--r--scene/debugger/scene_debugger.cpp103
-rw-r--r--scene/debugger/scene_debugger.h28
-rw-r--r--scene/gui/popup_menu.cpp21
-rw-r--r--scene/gui/tree.cpp11
-rw-r--r--scene/gui/tree.h1
-rw-r--r--scene/main/node.cpp10
-rw-r--r--scene/multiplayer/scene_cache_interface.cpp8
-rw-r--r--scene/multiplayer/scene_replication_interface.cpp5
-rw-r--r--scene/multiplayer/scene_rpc_interface.cpp11
-rw-r--r--servers/SCsub1
-rw-r--r--servers/debugger/SCsub5
-rw-r--r--servers/debugger/servers_debugger.cpp463
-rw-r--r--servers/debugger/servers_debugger.h132
-rw-r--r--servers/register_server_types.cpp5
-rw-r--r--servers/rendering/shader_language.cpp4
-rw-r--r--thirdparty/README.md4
-rw-r--r--thirdparty/harfbuzz/src/hb-buffer.hh37
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-glyf-table.hh4
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh10
-rw-r--r--thirdparty/harfbuzz/src/hb-version.h4
-rw-r--r--thirdparty/harfbuzz/src/hb.hh2
-rw-r--r--thirdparty/msdfgen/core/edge-coloring.cpp4
-rw-r--r--thirdparty/msdfgen/core/equation-solver.cpp47
70 files changed, 2062 insertions, 1337 deletions
diff --git a/SConstruct b/SConstruct
index b8063589c6..a27e5490a5 100644
--- a/SConstruct
+++ b/SConstruct
@@ -179,9 +179,11 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade
# Advanced options
opts.Add(BoolVariable("dev", "If yes, alias for verbose=yes warnings=extra werror=yes", False))
-opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))
opts.Add(BoolVariable("tests", "Build the unit tests", False))
+opts.Add(BoolVariable("fast_unsafe", "Enable unsafe options for faster rebuilds", False))
+opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False))
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
+opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))
opts.Add(EnumVariable("warnings", "Level of compilation warnings", "all", ("extra", "all", "moderate", "no")))
opts.Add(BoolVariable("werror", "Treat compiler warnings as errors", False))
opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all generated binary files", "")
@@ -360,6 +362,17 @@ if env_base["target"] == "debug":
# working on the engine itself.
env_base.Append(CPPDEFINES=["DEV_ENABLED"])
+# SCons speed optimization controlled by the `fast_unsafe` option, which provide
+# more than 10 s speed up for incremental rebuilds.
+# Unsafe as they reduce the certainty of rebuilding all changed files, so it's
+# enabled by default for `debug` builds, and can be overridden from command line.
+# Ref: https://github.com/SCons/scons/wiki/GoFastButton
+if methods.get_cmdline_bool("fast_unsafe", env_base["target"] == "debug"):
+ # Renamed to `content-timestamp` in SCons >= 4.2, keeping MD5 for compat.
+ env_base.Decider("MD5-timestamp")
+ env_base.SetOption("implicit_cache", 1)
+ env_base.SetOption("max_drift", 60)
+
if env_base["use_precise_math_checks"]:
env_base.Append(CPPDEFINES=["PRECISE_MATH_CHECKS"])
@@ -385,14 +398,15 @@ if selected_platform in platform_list:
else:
env = env_base.Clone()
- # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
- from SCons import __version__ as scons_raw_version
+ if env["compiledb"]:
+ # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
+ from SCons import __version__ as scons_raw_version
- scons_ver = env._get_major_minor_revision(scons_raw_version)
+ scons_ver = env._get_major_minor_revision(scons_raw_version)
- if scons_ver >= (4, 0, 0):
- env.Tool("compilation_db")
- env.Alias("compiledb", env.CompilationDatabase())
+ if scons_ver >= (4, 0, 0):
+ env.Tool("compilation_db")
+ env.Alias("compiledb", env.CompilationDatabase())
# 'dev' and 'production' are aliases to set default options if they haven't been set
# manually by the user.
@@ -817,6 +831,7 @@ elif selected_platform != "":
# The following only makes sense when the 'env' is defined, and assumes it is.
if "env" in locals():
+ # FIXME: This method mixes both cosmetic progress stuff and cache handling...
methods.show_progress(env)
# TODO: replace this with `env.Dump(format="json")`
# once we start requiring SCons 4.0 as min version.
diff --git a/core/SCsub b/core/SCsub
index c12dd4e60e..1379e9df9b 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -147,6 +147,7 @@ env.core_sources += thirdparty_obj
env.add_source_files(env.core_sources, "*.cpp")
env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp")
+env.add_source_files(env.core_sources, "version_hash.gen.cpp")
# Certificates
env.Depends(
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index d9abf5e5e9..ff8a8d283f 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -35,7 +35,6 @@
#include "core/donors.gen.h"
#include "core/license.gen.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
void Engine::set_physics_ticks_per_second(int p_ips) {
ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0.");
@@ -95,8 +94,8 @@ Dictionary Engine::get_version_info() const {
dict["build"] = VERSION_BUILD;
dict["year"] = VERSION_YEAR;
- String hash = VERSION_HASH;
- dict["hash"] = hash.length() == 0 ? String("unknown") : hash;
+ String hash = String(VERSION_HASH);
+ dict["hash"] = hash.is_empty() ? String("unknown") : hash;
String stringver = String(dict["major"]) + "." + String(dict["minor"]);
if ((int)dict["patch"] != 0) {
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 8d03f35617..195292f7e6 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -2376,21 +2376,18 @@ bool EngineDebugger::is_active() {
return ::EngineDebugger::is_active();
}
-void EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) {
- ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler already registered: " + p_name);
- profilers.insert(p_name, ProfilerCallable(p_toggle, p_add, p_tick));
- ProfilerCallable &p = profilers[p_name];
- ::EngineDebugger::Profiler profiler(
- &p,
- &EngineDebugger::call_toggle,
- &EngineDebugger::call_add,
- &EngineDebugger::call_tick);
- ::EngineDebugger::register_profiler(p_name, profiler);
+void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
+ ERR_FAIL_COND(p_profiler.is_null());
+ ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
+ ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
+ Error err = p_profiler->bind(p_name);
+ ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
+ profilers.insert(p_name, p_profiler);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
- ::EngineDebugger::unregister_profiler(p_name);
+ profilers[p_name]->unbind();
profilers.erase(p_name);
}
@@ -2435,45 +2432,6 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) {
::EngineDebugger::get_singleton()->send_message(p_msg, p_data);
}
-void EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) {
- Callable &toggle = ((ProfilerCallable *)p_user)->callable_toggle;
- if (toggle.is_null()) {
- return;
- }
- Variant enable = p_enable, opts = p_opts;
- const Variant *args[2] = { &enable, &opts };
- Variant retval;
- Callable::CallError err;
- toggle.call(args, 2, retval, err);
- ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'toggle' to callable: " + Variant::get_callable_error_text(toggle, args, 2, err));
-}
-
-void EngineDebugger::call_add(void *p_user, const Array &p_data) {
- Callable &add = ((ProfilerCallable *)p_user)->callable_add;
- if (add.is_null()) {
- return;
- }
- Variant data = p_data;
- const Variant *args[1] = { &data };
- Variant retval;
- Callable::CallError err;
- add.call(args, 1, retval, err);
- 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, 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;
- }
- Variant frame_time = p_frame_time, idle_time = p_idle_time, physics_time = p_physics_time, physics_frame_time = p_physics_frame_time;
- const Variant *args[4] = { &frame_time, &idle_time, &physics_time, &physics_frame_time };
- Variant retval;
- Callable::CallError err;
- tick.call(args, 4, retval, err);
- ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'tick' to callable: " + Variant::get_callable_error_text(tick, args, 4, err));
-}
-
Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
Callable &capture = *(Callable *)p_user;
if (capture.is_null()) {
@@ -2495,10 +2453,6 @@ EngineDebugger::~EngineDebugger() {
::EngineDebugger::unregister_message_capture(E.key);
}
captures.clear();
- for (const KeyValue<StringName, ProfilerCallable> &E : profilers) {
- ::EngineDebugger::unregister_profiler(E.key);
- }
- profilers.clear();
}
EngineDebugger *EngineDebugger::singleton = nullptr;
@@ -2506,8 +2460,9 @@ EngineDebugger *EngineDebugger::singleton = nullptr;
void EngineDebugger::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_active"), &EngineDebugger::is_active);
- ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &EngineDebugger::register_profiler);
+ ClassDB::bind_method(D_METHOD("register_profiler", "name", "profiler"), &EngineDebugger::register_profiler);
ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &EngineDebugger::unregister_profiler);
+
ClassDB::bind_method(D_METHOD("is_profiling", "name"), &EngineDebugger::is_profiling);
ClassDB::bind_method(D_METHOD("has_profiler", "name"), &EngineDebugger::has_profiler);
diff --git a/core/core_bind.h b/core/core_bind.h
index ac0e92a87a..21a1fc2077 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -31,6 +31,7 @@
#ifndef CORE_BIND_H
#define CORE_BIND_H
+#include "core/debugger/engine_profiler.h"
#include "core/io/compression.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
@@ -673,25 +674,8 @@ public:
class EngineDebugger : public Object {
GDCLASS(EngineDebugger, Object);
- class ProfilerCallable {
- friend class EngineDebugger;
-
- Callable callable_toggle;
- Callable callable_add;
- Callable callable_tick;
-
- public:
- ProfilerCallable() {}
-
- ProfilerCallable(const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) {
- callable_toggle = p_toggle;
- callable_add = p_add;
- callable_tick = p_tick;
- }
- };
-
Map<StringName, Callable> captures;
- Map<StringName, ProfilerCallable> profilers;
+ Map<StringName, Ref<EngineProfiler>> profilers;
protected:
static void _bind_methods();
@@ -702,7 +686,7 @@ public:
bool is_active();
- void register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick);
+ void register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler);
void unregister_profiler(const StringName &p_name);
bool is_profiling(const StringName &p_name);
bool has_profiler(const StringName &p_name);
@@ -715,9 +699,6 @@ public:
void send_message(const String &p_msg, const Array &p_data);
- 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, 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/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp
index 1a746d59a3..4c69290c2e 100644
--- a/core/debugger/debugger_marshalls.cpp
+++ b/core/debugger/debugger_marshalls.cpp
@@ -35,159 +35,6 @@
#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
-Array DebuggerMarshalls::ResourceUsage::serialize() {
- infos.sort();
-
- Array arr;
- arr.push_back(infos.size() * 4);
- for (const ResourceInfo &E : infos) {
- arr.push_back(E.path);
- arr.push_back(E.format);
- arr.push_back(E.type);
- arr.push_back(E.vram);
- }
- return arr;
-}
-
-bool DebuggerMarshalls::ResourceUsage::deserialize(const Array &p_arr) {
- CHECK_SIZE(p_arr, 1, "ResourceUsage");
- uint32_t size = p_arr[0];
- CHECK_SIZE(p_arr, size, "ResourceUsage");
- int idx = 1;
- for (uint32_t i = 0; i < size / 4; i++) {
- ResourceInfo info;
- info.path = p_arr[idx];
- info.format = p_arr[idx + 1];
- info.type = p_arr[idx + 2];
- info.vram = p_arr[idx + 3];
- infos.push_back(info);
- }
- CHECK_END(p_arr, idx, "ResourceUsage");
- return true;
-}
-
-Array DebuggerMarshalls::ScriptFunctionSignature::serialize() {
- Array arr;
- arr.push_back(name);
- arr.push_back(id);
- return arr;
-}
-
-bool DebuggerMarshalls::ScriptFunctionSignature::deserialize(const Array &p_arr) {
- CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature");
- name = p_arr[0];
- id = p_arr[1];
- CHECK_END(p_arr, 2, "ScriptFunctionSignature");
- return true;
-}
-
-Array DebuggerMarshalls::NetworkProfilerFrame::serialize() {
- Array arr;
- arr.push_back(infos.size() * 6);
- for (int i = 0; i < infos.size(); ++i) {
- arr.push_back(uint64_t(infos[i].node));
- arr.push_back(infos[i].node_path);
- arr.push_back(infos[i].incoming_rpc);
- arr.push_back(infos[i].incoming_rset);
- arr.push_back(infos[i].outgoing_rpc);
- arr.push_back(infos[i].outgoing_rset);
- }
- return arr;
-}
-
-bool DebuggerMarshalls::NetworkProfilerFrame::deserialize(const Array &p_arr) {
- CHECK_SIZE(p_arr, 1, "NetworkProfilerFrame");
- uint32_t size = p_arr[0];
- CHECK_SIZE(p_arr, size, "NetworkProfilerFrame");
- infos.resize(size);
- int idx = 1;
- for (uint32_t i = 0; i < size / 6; ++i) {
- infos.write[i].node = uint64_t(p_arr[idx]);
- infos.write[i].node_path = p_arr[idx + 1];
- infos.write[i].incoming_rpc = p_arr[idx + 2];
- infos.write[i].incoming_rset = p_arr[idx + 3];
- infos.write[i].outgoing_rpc = p_arr[idx + 4];
- infos.write[i].outgoing_rset = p_arr[idx + 5];
- }
- CHECK_END(p_arr, idx, "NetworkProfilerFrame");
- return true;
-}
-
-Array DebuggerMarshalls::ServersProfilerFrame::serialize() {
- Array arr;
- arr.push_back(frame_number);
- arr.push_back(frame_time);
- arr.push_back(idle_time);
- arr.push_back(physics_time);
- arr.push_back(physics_frame_time);
- arr.push_back(script_time);
-
- arr.push_back(servers.size());
- for (int i = 0; i < servers.size(); i++) {
- ServerInfo &s = servers[i];
- arr.push_back(s.name);
- arr.push_back(s.functions.size() * 2);
- for (int j = 0; j < s.functions.size(); j++) {
- ServerFunctionInfo &f = s.functions[j];
- arr.push_back(f.name);
- arr.push_back(f.time);
- }
- }
-
- arr.push_back(script_functions.size() * 4);
- for (int i = 0; i < script_functions.size(); i++) {
- arr.push_back(script_functions[i].sig_id);
- arr.push_back(script_functions[i].call_count);
- arr.push_back(script_functions[i].self_time);
- arr.push_back(script_functions[i].total_time);
- }
- return arr;
-}
-
-bool DebuggerMarshalls::ServersProfilerFrame::deserialize(const Array &p_arr) {
- CHECK_SIZE(p_arr, 7, "ServersProfilerFrame");
- frame_number = p_arr[0];
- frame_time = p_arr[1];
- idle_time = p_arr[2];
- physics_time = p_arr[3];
- physics_frame_time = p_arr[4];
- script_time = p_arr[5];
- int servers_size = p_arr[6];
- int idx = 7;
- while (servers_size) {
- CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame");
- servers_size--;
- ServerInfo si;
- si.name = p_arr[idx];
- int sub_data_size = p_arr[idx + 1];
- idx += 2;
- CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame");
- for (int j = 0; j < sub_data_size / 2; j++) {
- ServerFunctionInfo sf;
- sf.name = p_arr[idx];
- sf.time = p_arr[idx + 1];
- idx += 2;
- si.functions.push_back(sf);
- }
- servers.push_back(si);
- }
- CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame");
- int func_size = p_arr[idx];
- idx += 1;
- CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame");
- for (int i = 0; i < func_size / 4; i++) {
- ScriptFunctionInfo fi;
- fi.sig_id = p_arr[idx];
- fi.call_count = p_arr[idx + 1];
- fi.self_time = p_arr[idx + 2];
- fi.total_time = p_arr[idx + 3];
- script_functions.push_back(fi);
- idx += 4;
- }
- CHECK_END(p_arr, idx, "ServersProfilerFrame");
- return true;
-}
-
Array DebuggerMarshalls::ScriptStackDump::serialize() {
Array arr;
arr.push_back(frames.size() * 3);
@@ -298,33 +145,3 @@ bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
CHECK_END(p_arr, idx, "OutputError");
return true;
}
-
-Array DebuggerMarshalls::VisualProfilerFrame::serialize() {
- Array arr;
- arr.push_back(frame_number);
- arr.push_back(areas.size() * 3);
- for (int i = 0; i < areas.size(); i++) {
- arr.push_back(areas[i].name);
- arr.push_back(areas[i].cpu_msec);
- arr.push_back(areas[i].gpu_msec);
- }
- return arr;
-}
-
-bool DebuggerMarshalls::VisualProfilerFrame::deserialize(const Array &p_arr) {
- CHECK_SIZE(p_arr, 2, "VisualProfilerFrame");
- frame_number = p_arr[0];
- int size = p_arr[1];
- CHECK_SIZE(p_arr, size, "VisualProfilerFrame");
- int idx = 2;
- areas.resize(size / 3);
- RS::FrameProfileArea *w = areas.ptrw();
- for (int i = 0; i < size / 3; i++) {
- w[i].name = p_arr[idx];
- w[i].cpu_msec = p_arr[idx + 1];
- w[i].gpu_msec = p_arr[idx + 2];
- idx += 3;
- }
- CHECK_END(p_arr, idx, "VisualProfilerFrame");
- return true;
-}
diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h
index fae766812a..378c3af8aa 100644
--- a/core/debugger/debugger_marshalls.h
+++ b/core/debugger/debugger_marshalls.h
@@ -32,86 +32,8 @@
#define DEBUGGER_MARSHARLLS_H
#include "core/object/script_language.h"
-#include "servers/rendering_server.h"
struct DebuggerMarshalls {
- // Memory usage
- struct ResourceInfo {
- String path;
- String format;
- String type;
- RID id;
- int vram = 0;
- bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
- };
-
- struct ResourceUsage {
- List<ResourceInfo> infos;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
-
- // Network profiler
- struct MultiplayerNodeInfo {
- ObjectID node;
- String node_path;
- int incoming_rpc = 0;
- int incoming_rset = 0;
- int outgoing_rpc = 0;
- int outgoing_rset = 0;
- };
-
- struct NetworkProfilerFrame {
- Vector<MultiplayerNodeInfo> infos;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
-
- // Script Profiler
- class ScriptFunctionSignature {
- public:
- StringName name;
- int id = -1;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
-
- struct ScriptFunctionInfo {
- StringName name;
- int sig_id = -1;
- int call_count = 0;
- double self_time = 0;
- double total_time = 0;
- };
-
- // Servers profiler
- struct ServerFunctionInfo {
- StringName name;
- double time = 0;
- };
-
- struct ServerInfo {
- StringName name;
- List<ServerFunctionInfo> functions;
- };
-
- struct ServersProfilerFrame {
- int frame_number = 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;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
-
struct ScriptStackVariable {
String name;
Variant value;
@@ -145,15 +67,6 @@ struct DebuggerMarshalls {
Array serialize();
bool deserialize(const Array &p_arr);
};
-
- // Visual Profiler
- struct VisualProfilerFrame {
- uint64_t frame_number = 0;
- Vector<RS::FrameProfileArea> areas;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
};
#endif // DEBUGGER_MARSHARLLS_H
diff --git a/core/debugger/engine_profiler.cpp b/core/debugger/engine_profiler.cpp
new file mode 100644
index 0000000000..c858b1febd
--- /dev/null
+++ b/core/debugger/engine_profiler.cpp
@@ -0,0 +1,82 @@
+/*************************************************************************/
+/* engine_profiler.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "engine_profiler.h"
+
+#include "core/debugger/engine_debugger.h"
+
+void EngineProfiler::_bind_methods() {
+ GDVIRTUAL_BIND(_toggle, "enable", "options");
+ GDVIRTUAL_BIND(_add_frame, "data");
+ GDVIRTUAL_BIND(_tick, "frame_time", "idle_time", "physics_time", "physics_frame_time");
+}
+
+void EngineProfiler::toggle(bool p_enable, const Array &p_array) {
+ GDVIRTUAL_CALL(_toggle, p_enable, p_array);
+}
+
+void EngineProfiler::add(const Array &p_data) {
+ GDVIRTUAL_CALL(_add_frame, p_data);
+}
+
+void EngineProfiler::tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+ GDVIRTUAL_CALL(_tick, p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
+}
+
+Error EngineProfiler::bind(const String &p_name) {
+ ERR_FAIL_COND_V(is_bound(), ERR_ALREADY_IN_USE);
+ EngineDebugger::Profiler prof(
+ this,
+ [](void *p_user, bool p_enable, const Array &p_opts) {
+ ((EngineProfiler *)p_user)->toggle(p_enable, p_opts);
+ },
+ [](void *p_user, const Array &p_data) {
+ ((EngineProfiler *)p_user)->add(p_data);
+ },
+ [](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+ ((EngineProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
+ });
+ registration = p_name;
+ EngineDebugger::register_profiler(p_name, prof);
+ return OK;
+}
+
+Error EngineProfiler::unbind() {
+ ERR_FAIL_COND_V(!is_bound(), ERR_UNCONFIGURED);
+ EngineDebugger::unregister_profiler(registration);
+ registration.clear();
+ return OK;
+}
+
+EngineProfiler::~EngineProfiler() {
+ if (is_bound()) {
+ unbind();
+ }
+}
diff --git a/core/debugger/engine_profiler.h b/core/debugger/engine_profiler.h
new file mode 100644
index 0000000000..ade280a7bb
--- /dev/null
+++ b/core/debugger/engine_profiler.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* engine_profiler.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ENGINE_PROFILER_H
+#define ENGINE_PROFILER_H
+
+#include "core/object/ref_counted.h"
+
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/script_language.h"
+
+class EngineProfiler : public RefCounted {
+ GDCLASS(EngineProfiler, RefCounted);
+
+private:
+ String registration;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void toggle(bool p_enable, const Array &p_opts);
+ virtual void add(const Array &p_data);
+ virtual void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time);
+
+ Error bind(const String &p_name);
+ Error unbind();
+ bool is_bound() const { return registration.length() > 0; }
+
+ GDVIRTUAL2(_toggle, bool, Array);
+ GDVIRTUAL1(_add_frame, Array);
+ GDVIRTUAL4(_tick, double, double, double, double);
+
+ EngineProfiler() {}
+ virtual ~EngineProfiler();
+};
+
+#endif // ENGINE_PROFILER_H
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index 339aa9b61f..2fce23d003 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -33,32 +33,13 @@
#include "core/config/project_settings.h"
#include "core/debugger/debugger_marshalls.h"
#include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
#include "core/debugger/script_debugger.h"
#include "core/input/input.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
-#include "scene/main/node.h"
-#include "servers/display_server.h"
-
-template <typename T>
-void RemoteDebugger::_bind_profiler(const String &p_name, T *p_prof) {
- EngineDebugger::Profiler prof(
- p_prof,
- [](void *p_user, bool p_enable, const Array &p_opts) {
- ((T *)p_user)->toggle(p_enable, p_opts);
- },
- [](void *p_user, const Array &p_data) {
- ((T *)p_user)->add(p_data);
- },
- [](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);
-}
-struct RemoteDebugger::NetworkProfiler {
-public:
- typedef DebuggerMarshalls::MultiplayerNodeInfo NodeInfo;
+class RemoteDebugger::MultiplayerProfiler : public EngineProfiler {
struct BandwidthFrame {
uint32_t timestamp;
int packet_size;
@@ -70,11 +51,6 @@ public:
Vector<BandwidthFrame> bandwidth_out;
uint64_t last_bandwidth_time = 0;
- Map<ObjectID, NodeInfo> multiplayer_node_data;
- uint64_t last_profile_time = 0;
-
- NetworkProfiler() {}
-
int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
int total_bandwidth = 0;
@@ -96,22 +72,8 @@ public:
return total_bandwidth;
}
- void init_node(const ObjectID p_node) {
- if (multiplayer_node_data.has(p_node)) {
- return;
- }
- multiplayer_node_data.insert(p_node, DebuggerMarshalls::MultiplayerNodeInfo());
- multiplayer_node_data[p_node].node = p_node;
- multiplayer_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
- multiplayer_node_data[p_node].incoming_rpc = 0;
- multiplayer_node_data[p_node].incoming_rset = 0;
- multiplayer_node_data[p_node].outgoing_rpc = 0;
- multiplayer_node_data[p_node].outgoing_rset = 0;
- }
-
+public:
void toggle(bool p_enable, const Array &p_opts) {
- multiplayer_node_data.clear();
-
if (!p_enable) {
bandwidth_in.clear();
bandwidth_out.clear();
@@ -130,37 +92,18 @@ public:
}
void add(const Array &p_data) {
- ERR_FAIL_COND(p_data.size() < 1);
- const String type = p_data[0];
- if (type == "node") {
- ERR_FAIL_COND(p_data.size() < 3);
- const ObjectID id = p_data[1];
- const String what = p_data[2];
- init_node(id);
- NodeInfo &info = multiplayer_node_data[id];
- if (what == "rpc_in") {
- info.incoming_rpc++;
- } else if (what == "rpc_out") {
- info.outgoing_rpc++;
- } else if (what == "rset_in") {
- info.incoming_rset = 0;
- } else if (what == "rset_out") {
- info.outgoing_rset++;
- }
- } else if (type == "bandwidth") {
- ERR_FAIL_COND(p_data.size() < 4);
- const String inout = p_data[1];
- int time = p_data[2];
- int size = p_data[3];
- if (inout == "in") {
- bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
- bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
- bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
- } else if (inout == "out") {
- bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
- bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
- bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
- }
+ ERR_FAIL_COND(p_data.size() < 3);
+ const String inout = p_data[0];
+ int time = p_data[1];
+ int size = p_data[2];
+ if (inout == "in") {
+ bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
+ bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
+ bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
+ } else if (inout == "out") {
+ bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
+ bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
+ bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
}
}
@@ -174,208 +117,17 @@ public:
Array arr;
arr.push_back(incoming_bandwidth);
arr.push_back(outgoing_bandwidth);
- EngineDebugger::get_singleton()->send_message("network:bandwidth", arr);
- }
- if (pt - last_profile_time > 100) {
- last_profile_time = pt;
- DebuggerMarshalls::NetworkProfilerFrame frame;
- for (const KeyValue<ObjectID, NodeInfo> &E : multiplayer_node_data) {
- frame.infos.push_back(E.value);
- }
- multiplayer_node_data.clear();
- EngineDebugger::get_singleton()->send_message("network:profile_frame", frame.serialize());
- }
- }
-};
-
-struct RemoteDebugger::ScriptsProfiler {
- typedef DebuggerMarshalls::ScriptFunctionSignature FunctionSignature;
- typedef DebuggerMarshalls::ScriptFunctionInfo FunctionInfo;
- struct ProfileInfoSort {
- bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
- return A->total_time < B->total_time;
- }
- };
- Vector<ScriptLanguage::ProfilingInfo> info;
- Vector<ScriptLanguage::ProfilingInfo *> ptrs;
- Map<StringName, int> sig_map;
- int max_frame_functions = 16;
-
- void toggle(bool p_enable, const Array &p_opts) {
- if (p_enable) {
- sig_map.clear();
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->profiling_start();
- }
- if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) {
- max_frame_functions = MAX(0, int(p_opts[0]));
- }
- } else {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->profiling_stop();
- }
+ EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
}
}
-
- void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) {
- int ofs = 0;
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- if (p_accumulated) {
- ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs);
- } else {
- ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs);
- }
- }
-
- for (int i = 0; i < ofs; i++) {
- ptrs.write[i] = &info.write[i];
- }
-
- SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa;
- sa.sort(ptrs.ptrw(), ofs);
-
- int to_send = MIN(ofs, max_frame_functions);
-
- // Check signatures first, and compute total time.
- r_total = 0;
- for (int i = 0; i < to_send; i++) {
- if (!sig_map.has(ptrs[i]->signature)) {
- int idx = sig_map.size();
- FunctionSignature sig;
- sig.name = ptrs[i]->signature;
- sig.id = idx;
- EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize());
- sig_map[ptrs[i]->signature] = idx;
- }
- r_total += ptrs[i]->self_time;
- }
-
- // Send frame, script time, functions information then
- r_funcs.resize(to_send);
-
- FunctionInfo *w = r_funcs.ptrw();
- for (int i = 0; i < to_send; i++) {
- if (sig_map.has(ptrs[i]->signature)) {
- w[i].sig_id = sig_map[ptrs[i]->signature];
- }
- w[i].call_count = ptrs[i]->call_count;
- w[i].total_time = ptrs[i]->total_time / 1000000.0;
- w[i].self_time = ptrs[i]->self_time / 1000000.0;
- }
- }
-
- ScriptsProfiler() {
- info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
- ptrs.resize(info.size());
- }
};
-struct RemoteDebugger::ServersProfiler {
- bool skip_profile_frame = false;
- typedef DebuggerMarshalls::ServerInfo ServerInfo;
- typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
-
- Map<StringName, ServerInfo> server_data;
- ScriptsProfiler scripts_profiler;
-
- 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;
- if (p_enable) {
- server_data.clear(); // Clear old profiling data.
- } else {
- _send_frame_data(true); // Send final frame.
- }
- scripts_profiler.toggle(p_enable, p_opts);
- }
-
- void add(const Array &p_data) {
- String name = p_data[0];
- if (!server_data.has(name)) {
- ServerInfo info;
- info.name = name;
- server_data[name] = info;
- }
- ServerInfo &srv = server_data[name];
-
- ServerFunctionInfo fi;
- fi.name = p_data[1];
- fi.time = p_data[2];
- srv.functions.push_back(fi);
- }
-
- 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;
- physics_frame_time = p_physics_frame_time;
- _send_frame_data(false);
- }
-
- void _send_frame_data(bool p_final) {
- DebuggerMarshalls::ServersProfilerFrame frame;
- frame.frame_number = Engine::get_singleton()->get_process_frames();
- frame.frame_time = frame_time;
- frame.idle_time = idle_time;
- frame.physics_time = physics_time;
- frame.physics_frame_time = physics_frame_time;
- Map<StringName, ServerInfo>::Element *E = server_data.front();
- while (E) {
- if (!p_final) {
- frame.servers.push_back(E->get());
- }
- E->get().functions.clear();
- E = E->next();
- }
- uint64_t time = 0;
- scripts_profiler.write_frame_data(frame.script_functions, time, p_final);
- frame.script_time = USEC_TO_SEC(time);
- if (skip_profile_frame) {
- skip_profile_frame = false;
- return;
- }
- if (p_final) {
- EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize());
- } else {
- EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize());
- }
- }
-};
-
-struct RemoteDebugger::VisualProfiler {
- typedef DebuggerMarshalls::ServerInfo ServerInfo;
- typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
-
- Map<StringName, ServerInfo> server_data;
-
- void toggle(bool p_enable, const Array &p_opts) {
- RS::get_singleton()->set_frame_profiling_enabled(p_enable);
- }
-
- void add(const Array &p_data) {}
-
- 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()) {
- return;
- }
-
- frame.frame_number = RS::get_singleton()->get_frame_profile_frame();
- frame.areas.append_array(profile_areas);
- EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize());
- }
-};
-
-struct RemoteDebugger::PerformanceProfiler {
+class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
Object *performance = nullptr;
int last_perf_time = 0;
uint64_t last_monitor_modification_time = 0;
+public:
void toggle(bool p_enable, const Array &p_opts) {}
void add(const Array &p_data) {}
void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
@@ -421,29 +173,6 @@ struct RemoteDebugger::PerformanceProfiler {
}
};
-void RemoteDebugger::_send_resource_usage() {
- DebuggerMarshalls::ResourceUsage usage;
-
- List<RS::TextureInfo> tinfo;
- RS::get_singleton()->texture_debug_usage(&tinfo);
-
- for (const RS::TextureInfo &E : tinfo) {
- DebuggerMarshalls::ResourceInfo info;
- info.path = E.path;
- info.vram = E.bytes;
- info.id = E.texture;
- info.type = "Texture";
- if (E.depth == 0) {
- info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format);
- } else {
- info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format);
- }
- usage.infos.push_back(info);
- }
-
- EngineDebugger::get_singleton()->send_message("memory:usage", usage.serialize());
-}
-
Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
Array msg;
msg.push_back(p_message);
@@ -710,18 +439,12 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
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.
-
Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
}
- uint64_t loop_begin_usec = 0;
- uint64_t loop_time_sec = 0;
while (is_peer_connected()) {
- loop_begin_usec = OS::get_singleton()->get_ticks_usec();
-
flush_output();
peer->poll();
@@ -748,7 +471,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} else if (command == "continue") {
script_debugger->set_depth(-1);
script_debugger->set_lines_left(-1);
- DisplayServer::get_singleton()->window_move_to_foreground();
break;
} else if (command == "break") {
@@ -824,13 +546,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
OS::get_singleton()->delay_usec(10000);
OS::get_singleton()->process_and_drop_events();
}
-
- // This is for the camera override to stay live even when the game is paused from the editor
- loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f;
- RenderingServer::get_singleton()->sync();
- if (RenderingServer::get_singleton()->has_changed()) {
- RenderingServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale());
- }
}
send_message("debug_exit", Array());
@@ -897,8 +612,6 @@ Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bo
} else if (p_cmd == "set_skip_breakpoints") {
ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);
script_debugger->set_skip_breakpoints(p_data[0]);
- } else if (p_cmd == "memory") {
- _send_resource_usage();
} else if (p_cmd == "break") {
script_debugger->debug(script_debugger->get_break_language());
} else {
@@ -928,23 +641,15 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");
max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second");
- // Network Profiler
- network_profiler = memnew(NetworkProfiler);
- _bind_profiler("network", network_profiler);
-
- // Servers Profiler (audio/physics/...)
- servers_profiler = memnew(ServersProfiler);
- _bind_profiler("servers", servers_profiler);
-
- // Visual Profiler (cpu/gpu times)
- visual_profiler = memnew(VisualProfiler);
- _bind_profiler("visual", visual_profiler);
+ // Multiplayer Profiler
+ multiplayer_profiler.instantiate();
+ multiplayer_profiler->bind("multiplayer");
// Performance Profiler
Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
if (perf) {
- performance_profiler = memnew(PerformanceProfiler(perf));
- _bind_profiler("performance", performance_profiler);
+ performance_profiler = Ref<PerformanceProfiler>(memnew(PerformanceProfiler(perf)));
+ performance_profiler->bind("performance");
profiler_enable("performance", true);
}
@@ -973,17 +678,4 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
RemoteDebugger::~RemoteDebugger() {
remove_print_handler(&phl);
remove_error_handler(&eh);
-
- EngineDebugger::get_singleton()->unregister_profiler("servers");
- EngineDebugger::get_singleton()->unregister_profiler("network");
- EngineDebugger::get_singleton()->unregister_profiler("visual");
- if (EngineDebugger::has_profiler("performance")) {
- EngineDebugger::get_singleton()->unregister_profiler("performance");
- }
- memdelete(servers_profiler);
- memdelete(network_profiler);
- memdelete(visual_profiler);
- if (performance_profiler) {
- memdelete(performance_profiler);
- }
}
diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h
index bd64955c89..aada92da60 100644
--- a/core/debugger/remote_debugger.h
+++ b/core/debugger/remote_debugger.h
@@ -49,16 +49,11 @@ public:
private:
typedef DebuggerMarshalls::OutputError ErrorMessage;
- struct NetworkProfiler;
- struct ServersProfiler;
- struct ScriptsProfiler;
- struct VisualProfiler;
- struct PerformanceProfiler;
+ class MultiplayerProfiler;
+ class PerformanceProfiler;
- NetworkProfiler *network_profiler = nullptr;
- ServersProfiler *servers_profiler = nullptr;
- VisualProfiler *visual_profiler = nullptr;
- PerformanceProfiler *performance_profiler = nullptr;
+ Ref<MultiplayerProfiler> multiplayer_profiler;
+ Ref<PerformanceProfiler> performance_profiler;
Ref<RemoteDebuggerPeer> peer;
@@ -97,7 +92,6 @@ private:
bool is_peer_connected() { return peer->is_peer_connected(); }
void flush_output();
- void _send_resource_usage();
void _send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type);
Error _profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 123e3dc7b6..92ac5257b0 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -60,10 +60,10 @@ struct _NO_DISCARD_ Vector2 {
};
_FORCE_INLINE_ real_t &operator[](int p_idx) {
- return p_idx ? y : x;
+ return coord[p_idx];
}
_FORCE_INLINE_ const real_t &operator[](int p_idx) const {
- return p_idx ? y : x;
+ return coord[p_idx];
}
_FORCE_INLINE_ void set_all(const real_t p_value) {
diff --git a/core/math/vector2i.h b/core/math/vector2i.h
index 707c8c9490..3f5f12d4dd 100644
--- a/core/math/vector2i.h
+++ b/core/math/vector2i.h
@@ -43,19 +43,25 @@ struct _NO_DISCARD_ Vector2i {
};
union {
- int32_t x = 0;
- int32_t width;
- };
- union {
- int32_t y = 0;
- int32_t height;
+ struct {
+ union {
+ int32_t x;
+ int32_t width;
+ };
+ union {
+ int32_t y;
+ int32_t height;
+ };
+ };
+
+ int32_t coord[2] = { 0 };
};
_FORCE_INLINE_ int32_t &operator[](int p_idx) {
- return p_idx ? y : x;
+ return coord[p_idx];
}
_FORCE_INLINE_ const int32_t &operator[](int p_idx) const {
- return p_idx ? y : x;
+ return coord[p_idx];
}
_FORCE_INLINE_ Vector2i::Axis min_axis_index() const {
diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp
index c8cb333e2c..3533acd103 100644
--- a/core/multiplayer/multiplayer_api.cpp
+++ b/core/multiplayer/multiplayer_api.cpp
@@ -47,7 +47,6 @@ MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(Mul
void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {
if (EngineDebugger::is_profiling("multiplayer")) {
Array values;
- values.push_back("bandwidth");
values.push_back(p_inout);
values.push_back(OS::get_singleton()->get_ticks_msec());
values.push_back(p_size);
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 388368d181..a4b6a589f3 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -37,6 +37,7 @@
#include "core/crypto/aes_context.h"
#include "core/crypto/crypto.h"
#include "core/crypto/hashing_context.h"
+#include "core/debugger/engine_profiler.h"
#include "core/extension/native_extension.h"
#include "core/extension/native_extension_manager.h"
#include "core/input/input.h"
@@ -237,6 +238,8 @@ void register_core_types() {
GDREGISTER_VIRTUAL_CLASS(ResourceUID);
+ GDREGISTER_CLASS(EngineProfiler);
+
resource_uid = memnew(ResourceUID);
native_extension_manager = memnew(NativeExtensionManager);
diff --git a/core/version.h b/core/version.h
index c718d0f4d7..e22922fa66 100644
--- a/core/version.h
+++ b/core/version.h
@@ -68,4 +68,7 @@
// Example: "Godot v3.1.4.stable.official.mono"
#define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_FULL_BUILD
+// Git commit hash, generated at build time in `core/version_hash.gen.cpp`.
+extern const char *const VERSION_HASH;
+
#endif // VERSION_H
diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml
index 4e73d4d9d8..8fd01509ec 100644
--- a/doc/classes/Color.xml
+++ b/doc/classes/Color.xml
@@ -221,14 +221,15 @@
Returns a new color from [code]rgba[/code], an HTML hexadecimal color string. [code]rgba[/code] is not case sensitive, and may be prefixed with a '#' character.
[code]rgba[/code] must be a valid three-digit or six-digit hexadecimal color string, and may contain an alpha channel value. If [code]rgba[/code] does not contain an alpha channel value, an alpha channel value of 1.0 is applied.
If [code]rgba[/code] is invalid a Color(0.0, 0.0, 0.0, 1.0) is returned.
+ [b]Note:[/b] This method is not implemented in C#, but the same functionality is provided in the class constructor.
[codeblocks]
[gdscript]
var green = Color.html("#00FF00FF") # set green to Color(0.0, 1.0, 0.0, 1.0)
var blue = Color.html("#0000FF") # set blue to Color(0.0, 0.0, 1.0, 1.0)
[/gdscript]
[csharp]
- var green = Color.Html("#00FF00FF"); // set green to Color(0.0, 1.0, 0.0, 1.0)
- var blue = Color.Html("#0000FF"); // set blue to Color(0.0, 0.0, 1.0, 1.0)
+ var green = new Color("#00FF00FF"); // set green to Color(0.0, 1.0, 0.0, 1.0)
+ var blue = new Color("#0000FF"); // set blue to Color(0.0, 0.0, 1.0, 1.0)
[/csharp]
[/codeblocks]
</description>
diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml
index 861053b1c9..0e1bf99afc 100644
--- a/doc/classes/EngineDebugger.xml
+++ b/doc/classes/EngineDebugger.xml
@@ -65,14 +65,9 @@
<method name="register_profiler">
<return type="void" />
<argument index="0" name="name" type="StringName" />
- <argument index="1" name="toggle" type="Callable" />
- <argument index="2" name="add" type="Callable" />
- <argument index="3" name="tick" type="Callable" />
+ <argument index="1" name="profiler" type="EngineProfiler" />
<description>
- Registers a profiler with the given [code]name[/code].
- [code]toggle[/code] callable is called when the profiler is enabled/disabled. It must take an argument array as an argument.
- [code]add[/code] callable is called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data]. It must take a data array as argument.
- [code]tick[/code] callable is called at every active profiler iteration. It must take frame time, idle time, physics time, and physics idle time as arguments.
+ Registers a profiler with the given [code]name[/code]. See [EngineProfiler] for more informations.
</description>
</method>
<method name="send_message">
diff --git a/doc/classes/EngineProfiler.xml b/doc/classes/EngineProfiler.xml
new file mode 100644
index 0000000000..88780b1a41
--- /dev/null
+++ b/doc/classes/EngineProfiler.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EngineProfiler" inherits="RefCounted" version="4.0">
+ <brief_description>
+ Base class for creating custom profilers.
+ </brief_description>
+ <description>
+ This class can be used to implement custom profilers that are able to interact with the engine and editor debugger.
+ See [EngineDebugger] and [EditorDebuggerPlugin] for more informations.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_add_frame" qualifiers="virtual">
+ <return type="void" />
+ <argument index="0" name="data" type="Array" />
+ <description>
+ Called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data].
+ </description>
+ </method>
+ <method name="_tick" qualifiers="virtual">
+ <return type="void" />
+ <argument index="0" name="frame_time" type="float" />
+ <argument index="1" name="idle_time" type="float" />
+ <argument index="2" name="physics_time" type="float" />
+ <argument index="3" name="physics_frame_time" type="float" />
+ <description>
+ Called once every engine iteration when the profiler is active with informations about the current frame.
+ </description>
+ </method>
+ <method name="_toggle" qualifiers="virtual">
+ <return type="void" />
+ <argument index="0" name="enable" type="bool" />
+ <argument index="1" name="options" type="Array" />
+ <description>
+ Called when the profiler is enabled/disabled, along with a set of [code]options[/code].
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml
index dc5dd47287..36bdd6e6e4 100644
--- a/doc/classes/Popup.xml
+++ b/doc/classes/Popup.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Popup" inherits="Window" version="4.0">
<brief_description>
- Base container control for popups and dialogs.
+ Popup is a base window container for popup-like subwindows.
</brief_description>
<description>
- Popup is a base [Control] used to show dialogs and popups. It's a subwindow and modal by default (see [Control]) and has helpers for custom popup behavior.
+ Popup is a base window container for popup-like subwindows. It's a modal by default (see [member close_on_parent_focus]) and has helpers for custom popup behavior.
</description>
<tutorials>
</tutorials>
<members>
<member name="borderless" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" />
<member name="close_on_parent_focus" type="bool" setter="set_close_on_parent_focus" getter="get_close_on_parent_focus" default="true">
- If [code]true[/code], the [Popup] will close when its parent is focused.
+ If true, the [Popup] will close when its parent [Window] is focused.
</member>
<member name="transient" type="bool" setter="set_transient" getter="is_transient" overrides="Window" default="true" />
<member name="unresizable" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" />
@@ -21,7 +21,7 @@
<signals>
<signal name="popup_hide">
<description>
- Emitted when a popup is hidden.
+ Emitted when the popup is hidden.
</description>
</signal>
</signals>
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index b316f822f0..29a4cfcd46 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -4,7 +4,7 @@
PopupMenu displays a list of options.
</brief_description>
<description>
- [PopupMenu] is a [Control] that displays a list of options. They are popular in toolbars or context menus.
+ [PopupMenu] is a modal window used to display a list of options. They are popular in toolbars or context menus.
The size of a [PopupMenu] can be limited by using [member Window.max_size]. If the height of the list of items is larger than the maximum height of the [PopupMenu], a [ScrollContainer] within the popup will allow the user to scroll the contents.
If no maximum size is set, or if it is set to 0, the [PopupMenu] height will be limited by its parent rect.
</description>
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 4b051c4938..35a70ae53f 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -72,6 +72,13 @@
[b]Note:[/b] Despite the name of this method, the focus cursor itself is only visible in [constant SELECT_MULTI] mode.
</description>
</method>
+ <method name="get_button_id_at_position" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="position" type="Vector2" />
+ <description>
+ Returns the button id at [code]position[/code], or -1 if no button is there.
+ </description>
+ </method>
<method name="get_column_at_position" qualifiers="const">
<return type="int" />
<argument index="0" name="position" type="Vector2" />
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 12c91cdd10..675c534e7b 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -14,11 +14,11 @@
<return type="void" />
<argument index="0" name="column" type="int" />
<argument index="1" name="button" type="Texture2D" />
- <argument index="2" name="button_idx" type="int" default="-1" />
+ <argument index="2" name="id" type="int" default="-1" />
<argument index="3" name="disabled" type="bool" default="false" />
<argument index="4" name="tooltip" type="String" default="&quot;&quot;" />
<description>
- Adds a button with [Texture2D] [code]button[/code] at column [code]column[/code]. The [code]button_idx[/code] index is used to identify the button when calling other methods. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately after this method. Optionally, the button can be [code]disabled[/code] and have a [code]tooltip[/code].
+ Adds a button with [Texture2D] [code]button[/code] at column [code]column[/code]. The [code]id[/code] is used to identify the button. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately after this method. Optionally, the button can be [code]disabled[/code] and have a [code]tooltip[/code].
</description>
</method>
<method name="call_recursive" qualifiers="vararg">
@@ -80,6 +80,14 @@
Returns the [Texture2D] of the button at index [code]button_idx[/code] in column [code]column[/code].
</description>
</method>
+ <method name="get_button_by_id" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="column" type="int" />
+ <argument index="1" name="id" type="int" />
+ <description>
+ Returns the button index if there is a button with id [code]id[/code] in column [code]column[/code], otherwise returns -1.
+ </description>
+ </method>
<method name="get_button_count" qualifiers="const">
<return type="int" />
<argument index="0" name="column" type="int" />
@@ -87,6 +95,14 @@
Returns the number of buttons in column [code]column[/code]. May be used to get the most recently added button's index, if no index was specified.
</description>
</method>
+ <method name="get_button_id" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="column" type="int" />
+ <argument index="1" name="button_idx" type="int" />
+ <description>
+ Returns the id for the button at index [code]button_idx[/code] in column [code]column[/code].
+ </description>
+ </method>
<method name="get_button_tooltip" qualifiers="const">
<return type="String" />
<argument index="0" name="column" type="int" />
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index ab8f51ced5..82bb74683f 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -435,7 +435,7 @@
<constant name="CONTENT_SCALE_ASPECT_EXPAND" value="4" enum="ContentScaleAspect">
</constant>
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
- Automatic layout direction, determined from the parent control layout direction.
+ Automatic layout direction, determined from the parent window layout direction.
</constant>
<constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale.
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index 44455b927b..689d76ba26 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -273,9 +273,9 @@ Error VulkanContext::_obtain_vulkan_version() {
uint32_t api_version;
VkResult res = func(&api_version);
if (res == VK_SUCCESS) {
- vulkan_major = VK_VERSION_MAJOR(api_version);
- vulkan_minor = VK_VERSION_MINOR(api_version);
- vulkan_patch = VK_VERSION_PATCH(api_version);
+ vulkan_major = VK_API_VERSION_MAJOR(api_version);
+ vulkan_minor = VK_API_VERSION_MINOR(api_version);
+ vulkan_patch = VK_API_VERSION_PATCH(api_version);
} else {
// according to the documentation this shouldn't fail with anything except a memory allocation error
// in which case we're in deep trouble anyway
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index da376c588e..67cdba043a 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -35,6 +35,8 @@
#include "scene/gui/view_panner.h"
#include "scene/resources/text_line.h"
+#include <limits.h>
+
float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {
float h = p_h;
h = (h - v_scroll) / v_zoom;
@@ -55,15 +57,16 @@ static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, con
void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
float scale = timeline->get_zoom_scale();
+
int limit = timeline->get_name_limit();
- int right_limit = get_size().width - timeline->get_buttons_width();
+ int right_limit = get_size().width;
//selection may have altered the order of keys
Map<float, int> key_order;
for (int i = 0; i < animation->track_get_key_count(p_track); i++) {
float ofs = animation->track_get_key_time(p_track, i);
- if (moving_selection && track == p_track && selection.has(i)) {
+ if (moving_selection && selection.has(IntPair(p_track, i))) {
ofs += moving_selection_offset.x;
}
@@ -82,11 +85,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
float offset = animation->track_get_key_time(p_track, i);
float height = animation->bezier_track_get_key_value(p_track, i);
Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i);
- if (track == p_track && moving_handle != 0 && moving_handle_key == i) {
+ if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i) {
out_handle = moving_handle_right;
}
- if (moving_selection && track == p_track && selection.has(i)) {
+ if (moving_selection && selection.has(IntPair(p_track, i))) {
offset += moving_selection_offset.x;
height += moving_selection_offset.y;
}
@@ -96,11 +99,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
float offset_n = animation->track_get_key_time(p_track, i_n);
float height_n = animation->bezier_track_get_key_value(p_track, i_n);
Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);
- if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) {
+ if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i_n) {
in_handle = moving_handle_left;
}
- if (moving_selection && track == p_track && selection.has(i_n)) {
+ if (moving_selection && selection.has(IntPair(p_track, i_n))) {
offset_n += moving_selection_offset.x;
height_n += moving_selection_offset.y;
}
@@ -221,20 +224,10 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
}
if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
- close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons")));
-
bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons"));
bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons"));
selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons"));
}
- if (p_what == NOTIFICATION_RESIZED) {
- int right_limit = get_size().width - timeline->get_buttons_width();
- int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList"));
- int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList"));
-
- right_column->set_position(Vector2(right_limit + hsep, vsep));
- right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2));
- }
if (p_what == NOTIFICATION_DRAW) {
if (animation.is_null()) {
return;
@@ -258,101 +251,191 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE));
- int right_limit = get_size().width - timeline->get_buttons_width();
+ int right_limit = get_size().width;
- draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE));
-
- String base_path = animation->track_get_path(track);
- int end = base_path.find(":");
- if (end != -1) {
- base_path = base_path.substr(0, end + 1);
- }
-
- // NAMES AND ICON
int vofs = vsep;
int margin = 0;
- {
- NodePath path = animation->track_get_path(track);
+ Map<int, Color> subtrack_colors;
+ Color selected_track_color;
+ subtracks.clear();
+ subtrack_icons.clear();
+
+ Map<String, Vector<int>> track_indices;
+ int track_count = animation->get_track_count();
+ for (int i = 0; i < track_count; ++i) {
+ if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {
+ continue;
+ }
- Node *node = nullptr;
+ String base_path = animation->track_get_path(i);
+ if (is_filtered) {
+ if (root && root->has_node(base_path)) {
+ Node *node = root->get_node(base_path);
+ if (!node) {
+ continue; // No node, no filter.
+ }
+ if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ continue; // Skip track due to not selected.
+ }
+ }
+ }
- if (root && root->has_node(path)) {
- node = root->get_node(path);
+ int end = base_path.find(":");
+ if (end != -1) {
+ base_path = base_path.substr(0, end + 1);
}
+ Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>();
+ indices.push_back(i);
+ track_indices[base_path] = indices;
+ }
- String text;
+ for (const KeyValue<String, Vector<int>> &E : track_indices) {
+ String base_path = E.key;
- if (node) {
- int ofs = 0;
+ Vector<int> tracks = E.value;
- Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node");
+ // NAMES AND ICON
+ {
+ NodePath path = animation->track_get_path(tracks[0]);
- text = node->get_name();
- ofs += hsep;
- ofs += icon->get_width();
+ Node *node = nullptr;
- TextLine text_buf = TextLine(text, font, font_size);
- text_buf.set_width(limit - ofs - hsep);
+ if (root && root->has_node(path)) {
+ node = root->get_node(path);
+ }
- int h = MAX(text_buf.get_size().y, icon->get_height());
+ String text;
- draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2));
+ if (node) {
+ int ofs = 0;
- margin = icon->get_width();
+ Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node");
- Vector2 string_pos = Point2(ofs, vofs + (h - text_buf.get_size().y) / 2 + text_buf.get_line_ascent());
- string_pos = string_pos.floor();
- text_buf.draw(get_canvas_item(), string_pos, color);
+ text = node->get_name();
+ ofs += hsep;
- vofs += h + vsep;
- }
- }
+ TextLine text_buf = TextLine(text, font, font_size);
+ text_buf.set_width(limit - ofs - icon->get_width() - hsep);
- // RELATED TRACKS TITLES
+ int h = MAX(text_buf.get_size().y, icon->get_height());
- Map<int, Color> subtrack_colors;
- subtracks.clear();
+ draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2));
+ ofs += icon->get_width();
- for (int i = 0; i < animation->get_track_count(); i++) {
- if (animation->track_get_type(i) != Animation::TYPE_BEZIER) {
- continue;
- }
- String path = animation->track_get_path(i);
- if (!path.begins_with(base_path)) {
- continue; //another node
+ margin = icon->get_width();
+
+ Vector2 string_pos = Point2(ofs, vofs);
+ string_pos = string_pos.floor();
+ text_buf.draw(get_canvas_item(), string_pos, color);
+
+ vofs += h + vsep;
+ }
}
- path = path.replace_first(base_path, "");
- Color cc = color;
- TextLine text_buf = TextLine(path, font, font_size);
- text_buf.set_width(limit - margin - hsep);
+ Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"));
+ float remove_hpos = limit - hsep - remove->get_width();
+
+ Ref<Texture2D> lock = get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"));
+ Ref<Texture2D> unlock = get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"));
+ float lock_hpos = remove_hpos - hsep - lock->get_width();
+
+ Ref<Texture2D> visible = get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"));
+ Ref<Texture2D> hidden = get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"));
+ float visibility_hpos = lock_hpos - hsep - visible->get_width();
+
+ Ref<Texture2D> solo = get_theme_icon(SNAME("AudioBusSolo"), SNAME("EditorIcons"));
+ float solo_hpos = visibility_hpos - hsep - solo->get_width();
+
+ float buttons_width = remove->get_width() + lock->get_width() + visible->get_width() + solo->get_width() + hsep * 3;
+
+ for (int i = 0; i < tracks.size(); ++i) {
+ // RELATED TRACKS TITLES
+
+ int current_track = tracks[i];
+
+ String path = animation->track_get_path(current_track);
+ path = path.replace_first(base_path, "");
+
+ Color cc = color;
+ TextLine text_buf = TextLine(path, font, font_size);
+ text_buf.set_width(limit - margin - buttons_width);
+
+ Rect2 rect = Rect2(margin, vofs, solo_hpos - hsep - solo->get_width(), text_buf.get_size().y + vsep);
- Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, text_buf.get_size().y + vsep);
- if (i != track) {
cc.a *= 0.7;
- uint32_t hash = path.hash();
- hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
- hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
- hash = (hash >> 16) ^ hash;
- float h = (hash % 65535) / 65536.0;
- Color subcolor;
- subcolor.set_hsv(h, 0.2, 0.8);
- subcolor.a = 0.5;
- draw_rect(Rect2(0, vofs + text_buf.get_size().y * 0.1, margin - hsep, text_buf.get_size().y * 0.8), subcolor);
- subtrack_colors[i] = subcolor;
-
- subtracks[i] = rect;
- } else {
- Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- ac.a = 0.5;
- draw_rect(rect, ac);
- }
+ float h;
+ if (path.ends_with(":x")) {
+ h = 0;
+ } else if (path.ends_with(":y")) {
+ h = 0.33f;
+ } else if (path.ends_with(":z")) {
+ h = 0.66f;
+ } else {
+ uint32_t hash = path.hash();
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = (hash >> 16) ^ hash;
+ h = (hash % 65535) / 65536.0;
+ }
+
+ if (current_track != selected_track) {
+ Color track_color;
+ if (locked_tracks.has(current_track)) {
+ track_color.set_hsv(h, 0, 0.4);
+ } else {
+ track_color.set_hsv(h, 0.2, 0.8);
+ }
+ track_color.a = 0.5;
+ draw_rect(Rect2(0, vofs, margin - hsep, text_buf.get_size().y * 0.8), track_color);
+ subtrack_colors[current_track] = track_color;
+
+ subtracks[current_track] = rect;
+ } else {
+ Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ ac.a = 0.5;
+ draw_rect(rect, ac);
+ if (locked_tracks.has(selected_track)) {
+ selected_track_color.set_hsv(h, 0.0, 0.4);
+ } else {
+ selected_track_color.set_hsv(h, 0.8, 0.8);
+ }
+ }
+
+ Vector2 string_pos = Point2(margin, vofs);
+ text_buf.draw(get_canvas_item(), string_pos, cc);
+
+ float icon_start_height = vofs + rect.size.y / 2;
+ Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height());
+ draw_texture(remove, remove_rect.position);
+
+ Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height());
+ if (locked_tracks.has(current_track)) {
+ draw_texture(lock, lock_rect.position);
+ } else {
+ draw_texture(unlock, lock_rect.position);
+ }
+
+ Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2, visible->get_width(), visible->get_height());
+ if (hidden_tracks.has(current_track)) {
+ draw_texture(hidden, visible_rect.position);
+ } else {
+ draw_texture(visible, visible_rect.position);
+ }
- Vector2 string_pos = Point2(margin, vofs + text_buf.get_line_ascent());
- text_buf.draw(get_canvas_item(), string_pos, cc);
+ Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2, solo->get_width(), solo->get_height());
+ draw_texture(solo, solo_rect.position);
- vofs += text_buf.get_size().y + vsep;
+ Map<int, Rect2> track_icons;
+ track_icons[REMOVE_ICON] = remove_rect;
+ track_icons[LOCK_ICON] = lock_rect;
+ track_icons[VISIBILITY_ICON] = visible_rect;
+ track_icons[SOLO_ICON] = solo_rect;
+
+ subtrack_icons[current_track] = track_icons;
+
+ vofs += text_buf.get_size().y + vsep;
+ }
}
Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
@@ -398,6 +481,9 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
float scale = timeline->get_zoom_scale();
Ref<Texture2D> point = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons"));
for (const KeyValue<int, Color> &E : subtrack_colors) {
+ if (hidden_tracks.has(E.key)) {
+ continue;
+ }
_draw_track(E.key, E.value);
for (int i = 0; i < animation->track_get_key_count(E.key); i++) {
@@ -412,70 +498,116 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
}
}
- //draw edited curve
- const Color highlight = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
- _draw_track(track, highlight);
+ if (track_count > 0 && !hidden_tracks.has(selected_track)) {
+ //draw edited curve
+ _draw_track(selected_track, selected_track_color);
+ }
}
//draw editor handles
{
edit_points.clear();
-
float scale = timeline->get_zoom_scale();
- for (int i = 0; i < animation->track_get_key_count(track); i++) {
- float offset = animation->track_get_key_time(track, i);
- float value = animation->bezier_track_get_key_value(track, i);
- if (moving_selection && selection.has(i)) {
- offset += moving_selection_offset.x;
- value += moving_selection_offset.y;
+ for (int i = 0; i < track_count; ++i) {
+ if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) {
+ continue;
}
- Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));
-
- Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i);
- if (moving_handle != 0 && moving_handle_key == i) {
- in_vec = moving_handle_left;
+ if (hidden_tracks.has(i) || locked_tracks.has(i)) {
+ continue;
}
- Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));
- Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i);
+ int key_count = animation->track_get_key_count(i);
+ String path = animation->track_get_path(i);
- if (moving_handle != 0 && moving_handle_key == i) {
- out_vec = moving_handle_right;
+ if (is_filtered) {
+ if (root && root->has_node(path)) {
+ Node *node = root->get_node(path);
+ if (!node) {
+ continue; // No node, no filter.
+ }
+ if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ continue; // Skip track due to not selected.
+ }
+ }
}
- Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));
+ for (int j = 0; j < key_count; ++j) {
+ float offset = animation->track_get_key_time(i, j);
+ float value = animation->bezier_track_get_key_value(i, j);
+
+ if (moving_selection && selection.has(IntPair(i, j))) {
+ offset += moving_selection_offset.x;
+ value += moving_selection_offset.y;
+ }
- _draw_line_clipped(pos, pos_in, accent, limit, right_limit);
- _draw_line_clipped(pos, pos_out, accent, limit, right_limit);
+ Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));
- EditPoint ep;
- if (pos.x >= limit && pos.x <= right_limit) {
- ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor();
- ep.point_rect.size = bezier_icon->get_size();
- if (selection.has(i)) {
- draw_texture(selected_icon, ep.point_rect.position);
- draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
- draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
- } else {
- draw_texture(bezier_icon, ep.point_rect.position);
+ Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);
+ if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) {
+ in_vec = moving_handle_left;
+ }
+ Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));
+
+ Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);
+
+ if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) {
+ out_vec = moving_handle_right;
+ }
+
+ Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));
+
+ if (i == selected_track || selection.has(IntPair(i, j))) {
+ _draw_line_clipped(pos, pos_in, accent, limit, right_limit);
+ _draw_line_clipped(pos, pos_out, accent, limit, right_limit);
+ }
+
+ EditPoint ep;
+ ep.track = i;
+ ep.key = j;
+ if (pos.x >= limit && pos.x <= right_limit) {
+ ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor();
+ ep.point_rect.size = bezier_icon->get_size();
+ if (selection.has(IntPair(i, j))) {
+ draw_texture(selected_icon, ep.point_rect.position);
+ draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
+ draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
+ } else {
+ Color track_color = Color(1, 1, 1, 1);
+ if (i != selected_track) {
+ track_color = subtrack_colors[i];
+ }
+ draw_texture(bezier_icon, ep.point_rect.position, track_color);
+ }
+ ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);
+ }
+ if (i == selected_track || selection.has(IntPair(i, j))) {
+ if (pos_in.x >= limit && pos_in.x <= right_limit) {
+ ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor();
+ ep.in_rect.size = bezier_handle_icon->get_size();
+ draw_texture(bezier_handle_icon, ep.in_rect.position);
+ ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);
+ }
+ if (pos_out.x >= limit && pos_out.x <= right_limit) {
+ ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor();
+ ep.out_rect.size = bezier_handle_icon->get_size();
+ draw_texture(bezier_handle_icon, ep.out_rect.position);
+ ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5);
+ }
+ }
+ if (!locked_tracks.has(i)) {
+ edit_points.push_back(ep);
}
- ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);
- }
- if (pos_in.x >= limit && pos_in.x <= right_limit) {
- ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor();
- ep.in_rect.size = bezier_handle_icon->get_size();
- draw_texture(bezier_handle_icon, ep.in_rect.position);
- ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);
}
- if (pos_out.x >= limit && pos_out.x <= right_limit) {
- ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor();
- ep.out_rect.size = bezier_handle_icon->get_size();
- draw_texture(bezier_handle_icon, ep.out_rect.position);
- ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5);
+ }
+
+ for (int i = 0; i < edit_points.size(); ++i) {
+ if (edit_points[i].track == selected_track) {
+ EditPoint ep = edit_points[i];
+ edit_points.remove_at(i);
+ edit_points.insert(0, ep);
}
- edit_points.push_back(ep);
}
}
@@ -506,15 +638,7 @@ Ref<Animation> AnimationBezierTrackEdit::get_animation() const {
void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {
animation = p_animation;
- track = p_track;
- if (is_connected("select_key", Callable(editor, "_key_selected"))) {
- disconnect("select_key", Callable(editor, "_key_selected"));
- }
- if (is_connected("deselect_key", Callable(editor, "_key_deselected"))) {
- disconnect("deselect_key", Callable(editor, "_key_deselected"));
- }
- connect("select_key", Callable(editor, "_key_selected"), varray(p_track), CONNECT_DEFERRED);
- connect("deselect_key", Callable(editor, "_key_deselected"), varray(p_track), CONNECT_DEFERRED);
+ selected_track = p_track;
update();
}
@@ -529,11 +653,14 @@ void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) {
void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
timeline = p_timeline;
timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));
+ timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));
}
void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
editor = p_editor;
connect("clear_selection", Callable(editor, "_clear_selection"), varray(false));
+ connect("select_key", Callable(editor, "_key_selected"), varray(), CONNECT_DEFERRED);
+ connect("deselect_key", Callable(editor, "_key_deselected"), varray(), CONNECT_DEFERRED);
}
void AnimationBezierTrackEdit::_play_position_draw() {
@@ -544,9 +671,11 @@ void AnimationBezierTrackEdit::_play_position_draw() {
float scale = timeline->get_zoom_scale();
int h = get_size().height;
- int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit();
+ int limit = timeline->get_name_limit();
+
+ int px = (-timeline->get_value() + play_position_pos) * scale + limit;
- if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
+ if (px >= limit && px < (get_size().width)) {
Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
}
@@ -565,11 +694,84 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) {
root = p_root;
}
+void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
+ is_filtered = p_filtered;
+ if (animation == nullptr) {
+ return;
+ }
+ String base_path = animation->track_get_path(selected_track);
+ if (is_filtered) {
+ if (root && root->has_node(base_path)) {
+ Node *node = root->get_node(base_path);
+ if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ for (int i = 0; i < animation->get_track_count(); ++i) {
+ if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {
+ continue;
+ }
+
+ base_path = animation->track_get_path(i);
+ if (root && root->has_node(base_path)) {
+ node = root->get_node(base_path);
+ if (!node) {
+ continue; // No node, no filter.
+ }
+ if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ continue; // Skip track due to not selected.
+ }
+
+ set_animation_and_track(animation, i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ update();
+}
+
void AnimationBezierTrackEdit::_zoom_changed() {
update();
play_position->update();
}
+void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) {
+ if (locked_tracks.has(p_track)) {
+ locked_tracks.erase(p_track);
+ }
+
+ Vector<int> updated_locked_tracks;
+ for (Set<int>::Element *E = locked_tracks.front(); E; E = E->next()) {
+ updated_locked_tracks.push_back(E->get());
+ }
+ locked_tracks.clear();
+ for (int i = 0; i < updated_locked_tracks.size(); ++i) {
+ if (updated_locked_tracks[i] > p_track) {
+ locked_tracks.insert(updated_locked_tracks[i] - 1);
+ } else {
+ locked_tracks.insert(updated_locked_tracks[i]);
+ }
+ }
+}
+
+void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) {
+ if (hidden_tracks.has(p_track)) {
+ hidden_tracks.erase(p_track);
+ }
+
+ Vector<int> updated_hidden_tracks;
+ for (Set<int>::Element *E = hidden_tracks.front(); E; E = E->next()) {
+ updated_hidden_tracks.push_back(E->get());
+ }
+ hidden_tracks.clear();
+ for (int i = 0; i < updated_hidden_tracks.size(); ++i) {
+ if (updated_hidden_tracks[i] > p_track) {
+ hidden_tracks.insert(updated_hidden_tracks[i] - 1);
+ } else {
+ hidden_tracks.insert(updated_hidden_tracks[i]);
+ }
+ }
+}
+
String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {
return Control::get_tooltip(p_pos);
}
@@ -583,10 +785,10 @@ void AnimationBezierTrackEdit::_clear_selection() {
void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode) {
undo_redo->create_action(TTR("Update Selected Key Handles"));
double ratio = timeline->get_zoom_scale() * v_zoom;
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- const int key_index = E->get();
- undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, animation->bezier_track_get_key_handle_mode(track, key_index), ratio);
- undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, p_mode, ratio);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ const IntPair track_key_pair = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), ratio);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, p_mode, ratio);
}
undo_redo->commit_action();
}
@@ -606,8 +808,8 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int
int idx = animation->track_find_key(p_track, p_pos, true);
ERR_FAIL_COND(idx < 0);
- selection.insert(idx);
- emit_signal(SNAME("select_key"), idx, true);
+ selection.insert(IntPair(p_track, idx));
+ emit_signal(SNAME("select_key"), p_track, idx, true);
update();
}
@@ -631,14 +833,100 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ Ref<InputEventKey> key_press = p_event;
+
+ if (key_press.is_valid() && key_press->is_pressed()) {
+ if (ED_GET_SHORTCUT("animation_bezier_editor/focus")->matches_event(p_event)) {
+ SelectionSet focused_keys;
+ if (selection.is_empty()) {
+ for (int i = 0; i < edit_points.size(); ++i) {
+ IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key);
+ focused_keys.insert(key_pair);
+ }
+ } else {
+ for (SelectionSet::Element *E = selection.front(); E; E = E->next()) {
+ focused_keys.insert(E->get());
+ if (E->get().second > 0) {
+ IntPair previous_key = IntPair(E->get().first, E->get().second - 1);
+ focused_keys.insert(previous_key);
+ }
+ if (E->get().second < animation->track_get_key_count(E->get().first) - 1) {
+ IntPair next_key = IntPair(E->get().first, E->get().second + 1);
+ focused_keys.insert(next_key);
+ }
+ }
+ }
+ if (focused_keys.is_empty()) {
+ accept_event();
+ return;
+ }
+
+ float minimum_time = INFINITY;
+ float maximum_time = -INFINITY;
+ float minimum_value = INFINITY;
+ float maximum_value = -INFINITY;
+
+ for (SelectionSet::Element *E = focused_keys.front(); E; E = E->next()) {
+ IntPair key_pair = E->get();
+
+ float time = animation->track_get_key_time(key_pair.first, key_pair.second);
+ float value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second);
+
+ minimum_time = MIN(time, minimum_time);
+ maximum_time = MAX(time, maximum_time);
+ minimum_value = MIN(value, minimum_value);
+ maximum_value = MAX(value, maximum_value);
+ }
+
+ float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width();
+ float padding = width * 0.1;
+ float desired_scale = (width - padding / 2) / (maximum_time - minimum_time);
+ minimum_time = MAX(0, minimum_time - (padding / 2) / desired_scale);
+
+ float zv = Math::pow(100 / desired_scale, 0.125f);
+ if (zv < 1) {
+ zv = Math::pow(desired_scale / 100, 0.125f) - 1;
+ zv = 1 - zv;
+ }
+ float zoom_value = timeline->get_zoom()->get_max() - zv;
+
+ timeline->get_zoom()->set_value(zoom_value);
+ timeline->call_deferred("set_value", minimum_time);
+
+ v_scroll = (maximum_value + minimum_value) / 2.0;
+ v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9);
+
+ update();
+ accept_event();
+ return;
+ } else if (ED_GET_SHORTCUT("animation_bezier_editor/select_all_keys")->matches_event(p_event)) {
+ for (int i = 0; i < edit_points.size(); ++i) {
+ selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
+ }
+
+ update();
+ accept_event();
+ return;
+ } else if (ED_GET_SHORTCUT("animation_bezier_editor/deselect_all_keys")->matches_event(p_event)) {
+ selection.clear();
+
+ update();
+ accept_event();
+ return;
+ }
+ }
+
Ref<InputEventMouseButton> mb = p_event;
+ int limit = timeline->get_name_limit();
if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
menu_insert_key = mb->get_position();
- if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) {
+ if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {
Vector2 popup_pos = get_screen_position() + mb->get_position();
menu->clear();
- menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
+ if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) {
+ menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
+ }
if (selection.size()) {
menu->add_separator();
menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);
@@ -649,50 +937,163 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);
}
- menu->set_as_minsize();
- menu->set_position(popup_pos);
- menu->popup();
+ if (menu->get_item_count()) {
+ menu->set_as_minsize();
+ menu->set_position(popup_pos);
+ menu->popup();
+ }
}
}
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
for (const KeyValue<int, Rect2> &E : subtracks) {
if (E.value.has_point(mb->get_position())) {
- set_animation_and_track(animation, E.key);
- _clear_selection();
+ if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) {
+ set_animation_and_track(animation, E.key);
+ _clear_selection();
+ }
return;
}
}
+ for (const KeyValue<int, Map<int, Rect2>> &E : subtrack_icons) {
+ int track = E.key;
+ Map<int, Rect2> track_icons = E.value;
+ for (const KeyValue<int, Rect2> &I : track_icons) {
+ if (I.value.has_point(mb->get_position())) {
+ if (I.key == REMOVE_ICON) {
+ undo_redo->create_action("Remove Bezier Track");
+
+ undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
+ undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
+
+ undo_redo->add_do_method(animation.ptr(), "remove_track", track);
+
+ undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
+
+ for (int i = 0; i < animation->track_get_key_count(track); ++i) {
+ undo_redo->add_undo_method(
+ animation.ptr(),
+ "bezier_track_insert_key",
+ track, animation->track_get_key_time(track, i),
+ animation->bezier_track_get_key_value(track, i),
+ animation->bezier_track_get_key_in_handle(track, i),
+ animation->bezier_track_get_key_out_handle(track, i),
+ animation->bezier_track_get_key_handle_mode(track, i));
+ }
+
+ undo_redo->commit_action();
+
+ selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1);
+ return;
+ } else if (I.key == LOCK_ICON) {
+ if (locked_tracks.has(track)) {
+ locked_tracks.erase(track);
+ } else {
+ locked_tracks.insert(track);
+ if (selected_track == track) {
+ for (int i = 0; i < animation->get_track_count(); ++i) {
+ if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ set_animation_and_track(animation, i);
+ break;
+ }
+ }
+ }
+ }
+ update();
+ return;
+ } else if (I.key == VISIBILITY_ICON) {
+ if (hidden_tracks.has(track)) {
+ hidden_tracks.erase(track);
+ } else {
+ hidden_tracks.insert(track);
+ if (selected_track == track) {
+ for (int i = 0; i < animation->get_track_count(); ++i) {
+ if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ set_animation_and_track(animation, i);
+ break;
+ }
+ }
+ }
+ }
+
+ Vector<int> visible_tracks;
+ for (int i = 0; i < animation->get_track_count(); ++i) {
+ if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ visible_tracks.push_back(i);
+ }
+ }
+
+ if (visible_tracks.size() == 1) {
+ solo_track = visible_tracks[0];
+ } else {
+ solo_track = -1;
+ }
+
+ update();
+ return;
+ } else if (I.key == SOLO_ICON) {
+ if (solo_track == track) {
+ solo_track = -1;
+
+ hidden_tracks.clear();
+ } else {
+ if (hidden_tracks.has(track)) {
+ hidden_tracks.erase(track);
+ }
+ for (int i = 0; i < animation->get_track_count(); ++i) {
+ if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ if (i != track && !hidden_tracks.has(i)) {
+ hidden_tracks.insert(i);
+ }
+ }
+ }
+
+ set_animation_and_track(animation, track);
+ solo_track = track;
+ }
+ update();
+ return;
+ }
+ return;
+ }
+ }
+ }
+
for (int i = 0; i < edit_points.size(); i++) {
//first check point
//command makes it ignore the main point, so control point editors can be force-edited
//path 2D editing in the 3D and 2D editors works the same way
if (!mb->is_command_pressed()) {
if (edit_points[i].point_rect.has_point(mb->get_position())) {
+ IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);
if (mb->is_shift_pressed()) {
//add to selection
- if (selection.has(i)) {
- selection.erase(i);
+ if (selection.has(pair)) {
+ selection.erase(pair);
} else {
- selection.insert(i);
+ selection.insert(pair);
}
update();
- select_single_attempt = -1;
- } else if (selection.has(i)) {
+ select_single_attempt = IntPair(-1, -1);
+ } else if (selection.has(pair)) {
moving_selection_attempt = true;
moving_selection = false;
- moving_selection_from_key = i;
+ moving_selection_from_key = pair.second;
+ moving_selection_from_track = pair.first;
moving_selection_offset = Vector2();
- select_single_attempt = i;
+ select_single_attempt = pair;
update();
} else {
moving_selection_attempt = true;
moving_selection = true;
- moving_selection_from_key = i;
+ moving_selection_from_key = pair.second;
+ moving_selection_from_track = pair.first;
moving_selection_offset = Vector2();
+ set_animation_and_track(animation, pair.first);
selection.clear();
- selection.insert(i);
+ selection.insert(pair);
update();
}
return;
@@ -701,26 +1102,27 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (edit_points[i].in_rect.has_point(mb->get_position())) {
moving_handle = -1;
- moving_handle_key = i;
- moving_handle_left = animation->bezier_track_get_key_in_handle(track, i);
- moving_handle_right = animation->bezier_track_get_key_out_handle(track, i);
+ moving_handle_key = edit_points[i].key;
+ moving_handle_track = edit_points[i].track;
+ moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
update();
return;
}
if (edit_points[i].out_rect.has_point(mb->get_position())) {
moving_handle = 1;
- moving_handle_key = i;
- moving_handle_left = animation->bezier_track_get_key_in_handle(track, i);
- moving_handle_right = animation->bezier_track_get_key_out_handle(track, i);
+ moving_handle_key = edit_points[i].key;
+ moving_handle_track = edit_points[i].track;
+ moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
update();
return;
- ;
}
}
//insert new point
- if (mb->is_command_pressed() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) {
+ if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_pressed()) {
Array new_point;
new_point.resize(6);
@@ -733,34 +1135,35 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
new_point[4] = 0;
new_point[5] = 0;
- float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
- while (animation->track_find_key(track, time, true) != -1) {
+ float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
+ while (animation->track_find_key(selected_track, time, true) != -1) {
time += 0.001;
}
undo_redo->create_action(TTR("Add Bezier Point"));
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time);
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
undo_redo->commit_action();
//then attempt to move
- int index = animation->track_find_key(track, time, true);
+ int index = animation->track_find_key(selected_track, time, true);
ERR_FAIL_COND(index == -1);
_clear_selection();
- selection.insert(index);
+ selection.insert(IntPair(selected_track, index));
moving_selection_attempt = true;
moving_selection = false;
moving_selection_from_key = index;
+ moving_selection_from_track = selected_track;
moving_selection_offset = Vector2();
- select_single_attempt = -1;
+ select_single_attempt = IntPair(-1, -1);
update();
return;
}
//box select
- if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) {
+ if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) {
box_selecting_attempt = true;
box_selecting = false;
box_selecting_add = false;
@@ -786,14 +1189,44 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
Rect2 selection_rect(bs_from, bs_to - bs_from);
+ bool track_set = false;
for (int i = 0; i < edit_points.size(); i++) {
if (edit_points[i].point_rect.intersects(selection_rect)) {
- selection.insert(i);
+ selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
+ if (!track_set) {
+ track_set = true;
+ set_animation_and_track(animation, edit_points[i].track);
+ }
}
}
} else {
_clear_selection(); //clicked and nothing happened, so clear the selection
+
+ //select by clicking on curve
+ int track_count = animation->get_track_count();
+
+ float animation_length = animation->get_length();
+ animation->set_length(real_t(INT_MAX)); //bezier_track_interpolate doesn't find keys if they exist beyond anim length
+
+ float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
+
+ for (int i = 0; i < track_count; ++i) {
+ if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) {
+ continue;
+ }
+
+ float track_h = animation->bezier_track_interpolate(i, time);
+ float track_height = _bezier_h_to_pixel(track_h);
+
+ if (abs(mb->get_position().y - track_height) < 10) {
+ set_animation_and_track(animation, i);
+ break;
+ }
+ }
+
+ animation->set_length(animation_length);
}
+
box_selecting_attempt = false;
box_selecting = false;
update();
@@ -801,10 +1234,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
undo_redo->create_action(TTR("Move Bezier Points"));
- undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left);
- undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right);
- undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key));
- undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key));
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key));
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key));
undo_redo->commit_action();
moving_handle = 0;
@@ -819,60 +1252,60 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
List<AnimMoveRestore> to_restore;
// 1-remove the keys
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get());
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);
}
// 2- remove overlapped keys
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float newtime = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
- int idx = animation->track_find_key(track, newtime, true);
+ int idx = animation->track_find_key(E->get().first, newtime, true);
if (idx == -1) {
continue;
}
- if (selection.has(idx)) {
+ if (selection.has(IntPair(E->get().first, idx))) {
continue; //already in selection, don't save
}
- undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, newtime);
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);
AnimMoveRestore amr;
- amr.key = animation->track_get_key_value(track, idx);
- amr.track = track;
+ amr.key = animation->track_get_key_value(E->get().first, idx);
+ amr.track = E->get().first;
amr.time = newtime;
to_restore.push_back(amr);
}
// 3-move the keys (re insert them)
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
/*
if (newpos<0)
continue; //no add at the beginning
*/
- Array key = animation->track_get_key_value(track, E->get());
+ Array key = animation->track_get_key_value(E->get().first, E->get().second);
float h = key[0];
h += moving_selection_offset.y;
key[0] = h;
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1);
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1);
}
// 4-(undo) remove inserted keys
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
/*
if (newpos<0)
continue; //no remove what no inserted
*/
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, newpos);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);
}
// 5-(undo) reinsert keys
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float oldpos = animation->track_get_key_time(track, E->get());
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1);
}
// 6-(undo) reinsert overlapped keys
@@ -885,20 +1318,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
// 7-reselect
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float oldpos = animation->track_get_key_time(track, E->get());
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
float newpos = editor->snap_time(oldpos + moving_selection_offset.x);
- undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos);
- undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos);
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);
}
undo_redo->commit_action();
moving_selection = false;
- } else if (select_single_attempt != -1) {
+ } else if (select_single_attempt != IntPair(-1, -1)) {
selection.clear();
selection.insert(select_single_attempt);
+ set_animation_and_track(animation, select_single_attempt.first);
}
moving_selection_attempt = false;
@@ -909,13 +1343,13 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (moving_selection_attempt && mm.is_valid()) {
if (!moving_selection) {
moving_selection = true;
- select_single_attempt = -1;
+ select_single_attempt = IntPair(-1, -1);
}
float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
- float x = editor->snap_time(((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value());
+ float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value());
- moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key));
+ moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key));
update();
}
@@ -938,17 +1372,17 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
float x = editor->snap_time((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
- Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key));
+ Vector2 key_pos = Vector2(animation->track_get_key_time(selected_track, moving_handle_key), animation->bezier_track_get_key_value(selected_track, moving_handle_key));
Vector2 moving_handle_value = Vector2(x, y) - key_pos;
- moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key);
- moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key);
+ moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key);
if (moving_handle == -1) {
moving_handle_left = moving_handle_value;
- if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {
+ if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {
double ratio = timeline->get_zoom_scale() * v_zoom;
Transform2D xform;
xform.set_scale(Vector2(1.0, 1.0 / ratio));
@@ -961,7 +1395,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
} else if (moving_handle == 1) {
moving_handle_right = moving_handle_value;
- if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {
+ if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {
double ratio = timeline->get_zoom_scale() * v_zoom;
Transform2D xform;
xform.set_scale(Vector2(1.0, 1.0 / ratio));
@@ -980,12 +1414,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->create_action(TTR("Move Bezier Points"));
if (moving_handle == -1) {
double ratio = timeline->get_zoom_scale() * v_zoom;
- undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left, ratio);
- undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key), ratio);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio);
} else if (moving_handle == 1) {
double ratio = timeline->get_zoom_scale() * v_zoom;
- undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right, ratio);
- undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key), ratio);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);
}
undo_redo->commit_action();
@@ -1028,27 +1462,32 @@ void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_or
void AnimationBezierTrackEdit::_menu_selected(int p_index) {
switch (p_index) {
case MENU_KEY_INSERT: {
- Array new_point;
- new_point.resize(6);
+ if (animation->get_track_count() > 0) {
+ Array new_point;
+ new_point.resize(6);
- float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll;
+ float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll;
- new_point[0] = h;
- new_point[1] = -0.25;
- new_point[2] = 0;
- new_point[3] = 0.25;
- new_point[4] = 0;
- new_point[5] = Animation::HANDLE_MODE_BALANCED;
+ new_point[0] = h;
+ new_point[1] = -0.25;
+ new_point[2] = 0;
+ new_point[3] = 0.25;
+ new_point[4] = 0;
+ new_point[5] = Animation::HANDLE_MODE_BALANCED;
- float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
- while (animation->track_find_key(track, time, true) != -1) {
- time += 0.001;
- }
+ int limit = timeline->get_name_limit();
- undo_redo->create_action(TTR("Add Bezier Point"));
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time);
- undo_redo->commit_action();
+ float time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
+
+ while (animation->track_find_key(selected_track, time, true) != -1) {
+ time += 0.001;
+ }
+
+ undo_redo->create_action(TTR("Add Bezier Point"));
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
+ undo_redo->commit_action();
+ }
} break;
case MENU_KEY_DUPLICATE: {
@@ -1072,8 +1511,8 @@ void AnimationBezierTrackEdit::duplicate_selection() {
}
float top_time = 1e10;
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float t = animation->track_get_key_time(track, E->get());
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float t = animation->track_get_key_time(E->get().first, E->get().second);
if (t < top_time) {
top_time = t;
}
@@ -1083,21 +1522,21 @@ void AnimationBezierTrackEdit::duplicate_selection() {
List<Pair<int, float>> new_selection_values;
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- float t = animation->track_get_key_time(track, E->get());
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ float t = animation->track_get_key_time(E->get().first, E->get().second);
float dst_time = t + (timeline->get_play_position() - top_time);
- int existing_idx = animation->track_find_key(track, dst_time, true);
+ int existing_idx = animation->track_find_key(E->get().first, dst_time, true);
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get()));
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, dst_time);
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second));
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time);
Pair<int, float> p;
- p.first = track;
+ p.first = E->get().first;
p.second = dst_time;
new_selection_values.push_back(p);
if (existing_idx != -1) {
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx));
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, existing_idx), animation->track_get_key_transition(E->get().first, existing_idx));
}
}
@@ -1116,7 +1555,7 @@ void AnimationBezierTrackEdit::duplicate_selection() {
continue;
}
- selection.insert(existing_idx);
+ selection.insert(IntPair(track, existing_idx));
}
update();
@@ -1126,9 +1565,9 @@ void AnimationBezierTrackEdit::delete_selection() {
if (selection.size()) {
undo_redo->create_action(TTR("Anim Delete Keys"));
- for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
- undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get());
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1);
+ for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, animation->track_get_key_time(E->get().first, E->get().second), animation->track_get_key_value(E->get().first, E->get().second), 1);
}
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
@@ -1142,12 +1581,14 @@ void AnimationBezierTrackEdit::_bind_methods() {
ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection);
ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim);
ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim);
+ ClassDB::bind_method("_update_hidden_tracks_after", &AnimationBezierTrackEdit::_update_hidden_tracks_after);
+ ClassDB::bind_method("_update_locked_tracks_after", &AnimationBezierTrackEdit::_update_locked_tracks_after);
ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag")));
ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track")));
ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "ofs")));
- ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single")));
- ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index")));
+ ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single")));
+ ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index")));
ADD_SIGNAL(MethodInfo("clear_selection"));
ADD_SIGNAL(MethodInfo("close_request"));
@@ -1170,14 +1611,9 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
set_clip_contents(true);
- close_button = memnew(Button);
- close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request")));
- close_button->set_text(TTR("Close"));
-
- right_column = memnew(VBoxContainer);
- right_column->add_child(close_button);
- right_column->add_spacer();
- add_child(right_column);
+ ED_SHORTCUT("animation_bezier_editor/focus", TTR("Focus"), Key::F);
+ ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTR("Select All Keys"), KeyModifierMask::CMD | Key::A);
+ ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTR("Deselect All Keys"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::A);
menu = memnew(PopupMenu);
add_child(menu);
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index cf719a0355..fa6fc405f2 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -46,9 +46,6 @@ class AnimationBezierTrackEdit : public Control {
MENU_KEY_SET_HANDLE_BALANCED,
};
- VBoxContainer *right_column;
- Button *close_button;
-
AnimationTimelineEdit *timeline = nullptr;
UndoRedo *undo_redo = nullptr;
Node *root = nullptr;
@@ -56,7 +53,7 @@ class AnimationBezierTrackEdit : public Control {
float play_position_pos = 0;
Ref<Animation> animation;
- int track;
+ int selected_track;
Vector<Rect2> view_rects;
@@ -66,6 +63,19 @@ class AnimationBezierTrackEdit : public Control {
Map<int, Rect2> subtracks;
+ enum {
+ REMOVE_ICON,
+ LOCK_ICON,
+ SOLO_ICON,
+ VISIBILITY_ICON
+ };
+
+ Map<int, Map<int, Rect2>> subtrack_icons;
+ Set<int> locked_tracks;
+ Set<int> hidden_tracks;
+ int solo_track = -1;
+ bool is_filtered = false;
+
float v_scroll = 0;
float v_zoom = 1;
@@ -73,6 +83,9 @@ class AnimationBezierTrackEdit : public Control {
void _zoom_changed();
+ void _update_locked_tracks_after(int p_track);
+ void _update_hidden_tracks_after(int p_track);
+
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _menu_selected(int p_index);
@@ -80,10 +93,13 @@ class AnimationBezierTrackEdit : public Control {
Vector2 insert_at_pos;
+ typedef Pair<int, int> IntPair;
+
bool moving_selection_attempt = false;
- int select_single_attempt = -1;
+ IntPair select_single_attempt;
bool moving_selection = false;
int moving_selection_from_key;
+ int moving_selection_from_track;
Vector2 moving_selection_offset;
@@ -95,6 +111,7 @@ class AnimationBezierTrackEdit : public Control {
int moving_handle = 0; //0 no move -1 or +1 out
int moving_handle_key = 0;
+ int moving_handle_track = 0;
Vector2 moving_handle_left;
Vector2 moving_handle_right;
int moving_handle_mode; // value from Animation::HandleMode
@@ -119,11 +136,25 @@ class AnimationBezierTrackEdit : public Control {
Rect2 point_rect;
Rect2 in_rect;
Rect2 out_rect;
+ int track;
+ int key;
};
Vector<EditPoint> edit_points;
- Set<int> selection;
+ struct SelectionCompare {
+ bool operator()(const IntPair &lh, const IntPair &rh) {
+ if (lh.first == rh.first) {
+ return lh.second < rh.second;
+ } else {
+ return lh.first < rh.first;
+ }
+ }
+ };
+
+ typedef Set<IntPair, SelectionCompare> SelectionSet;
+
+ SelectionSet selection;
Ref<ViewPanner> panner;
void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
@@ -151,6 +182,7 @@ public:
void set_timeline(AnimationTimelineEdit *p_timeline);
void set_editor(AnimationTrackEditor *p_editor);
void set_root(Node *p_root);
+ void set_filtered(bool p_filtered);
void set_play_position(float p_pos);
void update_play_position();
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 2f33619a52..de924e84dc 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -570,7 +570,7 @@ public:
p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));
} break;
case Animation::TYPE_ROTATION_3D: {
- p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation"));
+ p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation"));
} break;
case Animation::TYPE_SCALE_3D: {
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
@@ -2118,23 +2118,19 @@ void AnimationTrackEdit::_notification(int p_what) {
update_mode_rect.position.y = 0;
update_mode_rect.size.y = get_size().height;
- ofs += update_icon->get_width() + hsep;
- update_mode_rect.size.x += hsep;
+ ofs += update_icon->get_width() + hsep / 2;
+ update_mode_rect.size.x += hsep / 2;
if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
update_mode_rect.size.x += down_icon->get_width();
- bezier_edit_rect = Rect2();
} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
update_mode_rect.size.x += down_icon->get_width();
- bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2;
- bezier_edit_rect.size = bezier_icon->get_size();
- draw_texture(bezier_icon, bezier_edit_rect.position);
+
update_mode_rect = Rect2();
} else {
update_mode_rect = Rect2();
- bezier_edit_rect = Rect2();
}
ofs += down_icon->get_width();
@@ -2160,8 +2156,8 @@ void AnimationTrackEdit::_notification(int p_what) {
interp_mode_rect.position.y = 0;
interp_mode_rect.size.y = get_size().height;
- ofs += icon->get_width() + hsep;
- interp_mode_rect.size.x += hsep;
+ ofs += icon->get_width() + hsep / 2;
+ interp_mode_rect.size.x += hsep / 2;
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
@@ -2193,8 +2189,8 @@ void AnimationTrackEdit::_notification(int p_what) {
loop_wrap_rect.position.y = 0;
loop_wrap_rect.size.y = get_size().height;
- ofs += icon->get_width() + hsep;
- loop_wrap_rect.size.x += hsep;
+ ofs += icon->get_width() + hsep / 2;
+ loop_wrap_rect.size.x += hsep / 2;
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
@@ -2213,7 +2209,7 @@ void AnimationTrackEdit::_notification(int p_what) {
Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons"));
- remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2;
+ remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width());
remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
remove_rect.size = icon->get_size();
@@ -2792,11 +2788,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (bezier_edit_rect.has_point(pos)) {
- emit_signal(SNAME("bezier_edit"));
- accept_event();
- }
-
// Check keyframes.
if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
@@ -3326,10 +3317,21 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
snap->set_disabled(false);
snap_mode->set_disabled(false);
+ bezier_edit_icon->set_disabled(true);
+
imported_anim_warning->hide();
+ bool import_warning_done = false;
+ bool bezier_done = false;
for (int i = 0; i < animation->get_track_count(); i++) {
if (animation->track_is_imported(i)) {
imported_anim_warning->show();
+ import_warning_done = true;
+ }
+ if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ bezier_edit_icon->set_disabled(false);
+ bezier_done = true;
+ }
+ if (import_warning_done && bezier_done) {
break;
}
}
@@ -3343,6 +3345,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
step->set_read_only(true);
snap->set_disabled(true);
snap_mode->set_disabled(true);
+ bezier_edit_icon->set_disabled(true);
}
}
@@ -4167,13 +4170,15 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
} break;
case Animation::TYPE_BEZIER: {
Array array;
- array.resize(5);
+ array.resize(6);
array[0] = p_id.value;
array[1] = -0.25;
array[2] = 0;
array[3] = 0.25;
array[4] = 0;
+ array[5] = Animation::HANDLE_MODE_BALANCED;
value = array;
+ bezier_edit_icon->set_disabled(false);
} break;
case Animation::TYPE_ANIMATION: {
@@ -4399,7 +4404,6 @@ void AnimationTrackEditor::_update_tracks() {
track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track), varray(i), CONNECT_DEFERRED);
track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected), varray(i), CONNECT_DEFERRED);
track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected), varray(i), CONNECT_DEFERRED);
- track_edit->connect("bezier_edit", callable_mp(this, &AnimationTrackEditor::_bezier_edit), varray(i), CONNECT_DEFERRED);
track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin));
track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection));
track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit));
@@ -4515,6 +4519,7 @@ void AnimationTrackEditor::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons")));
+ bezier_edit_icon->set_icon(get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")));
snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));
view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons")));
selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons")));
@@ -4630,6 +4635,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
adding_track_path = path_to;
prop_selector->set_type_filter(filter);
prop_selector->select_property_from_instance(node);
+ bezier_edit_icon->set_disabled(false);
} break;
case Animation::TYPE_AUDIO: {
if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) {
@@ -4946,7 +4952,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) {
EditorNode::get_singleton()->show_warning(TTR("Method not found in object: ") + p_method);
}
-void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) {
+void AnimationTrackEditor::_key_selected(int p_track, int p_key, bool p_single) {
ERR_FAIL_INDEX(p_track, animation->get_track_count());
ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
@@ -5294,6 +5300,20 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
}
}
+void AnimationTrackEditor::_toggle_bezier_edit() {
+ if (bezier_edit->is_visible()) {
+ _cancel_bezier_edit();
+ } else {
+ int track_count = animation->get_track_count();
+ for (int i = 0; i < track_count; ++i) {
+ if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
+ _bezier_edit(i);
+ return;
+ }
+ }
+ }
+}
+
void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
if (p_alt) {
if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) {
@@ -5322,6 +5342,7 @@ void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin
void AnimationTrackEditor::_cancel_bezier_edit() {
bezier_edit->hide();
scroll->show();
+ bezier_edit_icon->set_pressed(false);
}
void AnimationTrackEditor::_bezier_edit(int p_for_track) {
@@ -5908,6 +5929,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
void AnimationTrackEditor::_view_group_toggle() {
_update_tracks();
view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons")));
+ bezier_edit->set_filtered(selected_filter->is_pressed());
}
bool AnimationTrackEditor::is_grouping_tracks() {
@@ -6153,6 +6175,15 @@ AnimationTrackEditor::AnimationTrackEditor() {
bottom_hb->add_spacer();
+ bezier_edit_icon = memnew(Button);
+ bezier_edit_icon->set_flat(true);
+ bezier_edit_icon->set_disabled(true);
+ bezier_edit_icon->set_toggle_mode(true);
+ bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit));
+ bezier_edit_icon->set_tooltip(TTR("Toggle between the bezier curve editor and track editor."));
+
+ bottom_hb->add_child(bezier_edit_icon);
+
selected_filter = memnew(Button);
selected_filter->set_flat(true);
selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same.
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 50c5c692c0..edba784310 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -164,7 +164,6 @@ class AnimationTrackEdit : public Control {
Rect2 interp_mode_rect;
Rect2 loop_wrap_rect;
Rect2 remove_rect;
- Rect2 bezier_edit_rect;
Ref<Texture2D> type_icon;
Ref<Texture2D> selected_icon;
@@ -300,6 +299,7 @@ class AnimationTrackEditor : public VBoxContainer {
EditorSpinSlider *step;
TextureRect *zoom_icon;
Button *snap;
+ Button *bezier_edit_icon;
OptionButton *snap_mode;
Button *imported_anim_warning;
@@ -406,7 +406,7 @@ class AnimationTrackEditor : public VBoxContainer {
Map<SelectedKey, KeyInfo> selection;
- void _key_selected(int p_key, bool p_single, int p_track);
+ void _key_selected(int p_track, int p_key, bool p_single);
void _key_deselected(int p_key, int p_track);
bool moving_selection;
@@ -431,6 +431,7 @@ class AnimationTrackEditor : public VBoxContainer {
Vector<Ref<AnimationTrackEditPlugin>> track_edit_plugins;
+ void _toggle_bezier_edit();
void _cancel_bezier_edit();
void _bezier_edit(int p_for_track);
diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp
index 698e950f57..b05134144e 100644
--- a/editor/debugger/editor_network_profiler.cpp
+++ b/editor/debugger/editor_network_profiler.cpp
@@ -56,7 +56,7 @@ void EditorNetworkProfiler::_update_frame() {
TreeItem *root = counters_display->create_item();
- for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) {
+ for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) {
TreeItem *node = counters_display->create_item(root);
for (int j = 0; j < counters_display->get_columns(); ++j) {
@@ -65,9 +65,7 @@ void EditorNetworkProfiler::_update_frame() {
node->set_text(0, E.value.node_path);
node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc));
- node->set_text(2, E.value.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset));
- node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
- node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset));
+ node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
}
}
@@ -91,14 +89,12 @@ void EditorNetworkProfiler::_clear_pressed() {
}
}
-void EditorNetworkProfiler::add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame) {
+void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame) {
if (!nodes_data.has(p_frame.node)) {
nodes_data.insert(p_frame.node, p_frame);
} else {
nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
- nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset;
nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
- nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset;
}
if (frame_delay->is_stopped()) {
@@ -174,7 +170,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
counters_display->set_hide_folding(true);
counters_display->set_hide_root(true);
- counters_display->set_columns(5);
+ counters_display->set_columns(3);
counters_display->set_column_titles_visible(true);
counters_display->set_column_title(0, TTR("Node"));
counters_display->set_column_expand(0, true);
@@ -184,18 +180,10 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
counters_display->set_column_expand(1, false);
counters_display->set_column_clip_content(1, true);
counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE);
- counters_display->set_column_title(2, TTR("Incoming RSET"));
+ counters_display->set_column_title(2, TTR("Outgoing RPC"));
counters_display->set_column_expand(2, false);
counters_display->set_column_clip_content(2, true);
counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);
- counters_display->set_column_title(3, TTR("Outgoing RPC"));
- counters_display->set_column_expand(3, false);
- counters_display->set_column_clip_content(3, true);
- counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE);
- counters_display->set_column_title(4, TTR("Outgoing RSET"));
- counters_display->set_column_expand(4, false);
- counters_display->set_column_clip_content(4, true);
- counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE);
add_child(counters_display);
frame_delay = memnew(Timer);
diff --git a/editor/debugger/editor_network_profiler.h b/editor/debugger/editor_network_profiler.h
index 320dd2a826..3e95eb0de6 100644
--- a/editor/debugger/editor_network_profiler.h
+++ b/editor/debugger/editor_network_profiler.h
@@ -31,7 +31,7 @@
#ifndef EDITORNETWORKPROFILER_H
#define EDITORNETWORKPROFILER_H
-#include "core/debugger/debugger_marshalls.h"
+#include "scene/debugger/scene_debugger.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
@@ -50,7 +50,7 @@ private:
Timer *frame_delay;
- Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> nodes_data;
+ Map<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data;
void _update_frame();
@@ -62,7 +62,7 @@ protected:
static void _bind_methods();
public:
- void add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame);
+ void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame);
void set_bandwidth(int p_incoming, int p_outgoing);
bool is_profiling();
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 6aedfa6ccb..28e8edb26e 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -36,7 +36,6 @@
#include "core/io/marshalls.h"
#include "core/string/ustring.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "editor/debugger/debug_adapter/debug_adapter_protocol.h"
#include "editor/debugger/editor_network_profiler.h"
#include "editor/debugger/editor_performance_profiler.h"
@@ -64,6 +63,7 @@
#include "scene/gui/texture_button.h"
#include "scene/gui/tree.h"
#include "scene/resources/packed_scene.h"
+#include "servers/debugger/servers_debugger.h"
#include "servers/display_server.h"
using CameraOverride = EditorDebuggerNode::CameraOverride;
@@ -128,6 +128,7 @@ void ScriptEditorDebugger::debug_continue() {
_clear_execution();
_put_msg("continue", Array());
+ _put_msg("servers:foreground", Array());
}
void ScriptEditorDebugger::update_tabs() {
@@ -278,7 +279,7 @@ void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const
}
void ScriptEditorDebugger::_video_mem_request() {
- _put_msg("core:memory", Array());
+ _put_msg("servers:memory", Array());
}
void ScriptEditorDebugger::_video_mem_export() {
@@ -344,15 +345,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
if (id.is_valid()) {
emit_signal(SNAME("remote_object_updated"), id);
}
- } else if (p_msg == "memory:usage") {
+ } else if (p_msg == "servers:memory_usage") {
vmem_tree->clear();
TreeItem *root = vmem_tree->create_item();
- DebuggerMarshalls::ResourceUsage usage;
+ ServersDebugger::ResourceUsage usage;
usage.deserialize(p_data);
uint64_t total = 0;
- for (const DebuggerMarshalls::ResourceInfo &E : usage.infos) {
+ for (const ServersDebugger::ResourceInfo &E : usage.infos) {
TreeItem *it = vmem_tree->create_item(root);
String type = E.type;
int bytes = E.vram;
@@ -445,7 +446,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
performance_profiler->add_profile_frame(frame_data);
} else if (p_msg == "visual:profile_frame") {
- DebuggerMarshalls::VisualProfilerFrame frame;
+ ServersDebugger::VisualProfilerFrame frame;
frame.deserialize(p_data);
EditorVisualProfiler::Metric metric;
@@ -592,13 +593,13 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
} else if (p_msg == "servers:function_signature") {
// Cache a profiler signature.
- DebuggerMarshalls::ScriptFunctionSignature sig;
+ ServersDebugger::ScriptFunctionSignature sig;
sig.deserialize(p_data);
profiler_signature[sig.id] = sig.name;
} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
EditorProfiler::Metric metric;
- DebuggerMarshalls::ServersProfilerFrame frame;
+ ServersDebugger::ServersProfilerFrame frame;
frame.deserialize(p_data);
metric.valid = true;
metric.frame_number = frame.frame_number;
@@ -642,7 +643,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
}
for (int i = 0; i < frame.servers.size(); i++) {
- const DebuggerMarshalls::ServerInfo &srv = frame.servers[i];
+ const ServersDebugger::ServerInfo &srv = frame.servers[i];
EditorProfiler::Metric::Category c;
const String name = srv.name;
c.name = name.capitalize();
@@ -709,14 +710,14 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
profiler->add_frame_metric(metric, true);
}
- } else if (p_msg == "network:profile_frame") {
- DebuggerMarshalls::NetworkProfilerFrame frame;
+ } else if (p_msg == "multiplayer:rpc") {
+ SceneDebugger::RPCProfilerFrame frame;
frame.deserialize(p_data);
for (int i = 0; i < frame.infos.size(); i++) {
network_profiler->add_node_frame_data(frame.infos[i]);
}
- } else if (p_msg == "network:bandwidth") {
+ } else if (p_msg == "multiplayer:bandwidth") {
ERR_FAIL_COND(p_data.size() < 2);
network_profiler->set_bandwidth(p_data[0], p_data[1]);
@@ -833,6 +834,9 @@ void ScriptEditorDebugger::_notification(int p_what) {
msg.push_back(cam->get_far());
_put_msg("scene:override_camera_3D:transform", msg);
}
+ if (breaked) {
+ _put_msg("servers:draw", Array());
+ }
}
const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20;
@@ -971,7 +975,8 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
data.push_back(p_enable);
switch (p_type) {
case PROFILER_NETWORK:
- _put_msg("profiler:network", data);
+ _put_msg("profiler:multiplayer", data);
+ _put_msg("profiler:rpc", data);
break;
case PROFILER_VISUAL:
_put_msg("profiler:visual", data);
@@ -1543,19 +1548,10 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) {
const int line_number = file_line_number[1].to_int();
// Construct a GitHub repository URL and open it in the user's default web browser.
- if (String(VERSION_HASH).length() >= 1) {
- // Git commit hash information available; use it for greater accuracy, including for development versions.
- OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d",
- VERSION_HASH,
- file,
- line_number));
- } else {
- // Git commit hash information unavailable; fall back to tagged releases.
- OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s-stable/%s#L%d",
- VERSION_NUMBER,
- file,
- line_number));
- }
+ // If the commit hash is available, use it for greater accuracy. Otherwise fall back to tagged release.
+ String git_ref = String(VERSION_HASH).is_empty() ? String(VERSION_NUMBER) + "-stable" : String(VERSION_HASH);
+ OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d",
+ git_ref, file, line_number));
} break;
case ACTION_DELETE_BREAKPOINT: {
const TreeItem *selected = breakpoints_tree->get_selected();
diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp
index 54377971c6..4309f55a2b 100644
--- a/editor/editor_about.cpp
+++ b/editor/editor_about.cpp
@@ -29,13 +29,12 @@
/*************************************************************************/
#include "editor_about.h"
-#include "editor_node.h"
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/license.gen.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
+#include "editor_node.h"
// The metadata key used to store and retrieve the version text to copy to the clipboard.
static const String META_TEXT_TO_COPY = "text_to_copy";
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 10ce7228e0..4b2f1c5104 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -47,7 +47,6 @@
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/gui/center_container.h"
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index f79b5027cb..e35af6dd64 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -886,15 +886,13 @@ void Node3DEditorViewport::_update_name() {
view_menu->reset_size();
}
-void Node3DEditorViewport::_compute_edit(const Point2 &p_point, const bool p_auto_center) {
+void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {
_edit.original_local = spatial_editor->are_local_coords_enabled();
_edit.click_ray = _get_ray(p_point);
_edit.click_ray_pos = _get_ray_pos(p_point);
_edit.plane = TRANSFORM_VIEW;
- if (p_auto_center) {
- _edit.center = spatial_editor->get_gizmo_transform().origin;
- }
spatial_editor->update_transform_gizmo();
+ _edit.center = spatial_editor->get_gizmo_transform().origin;
Node3D *selected = spatial_editor->get_single_selected_node();
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
@@ -1366,7 +1364,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
- _edit.center = spatial_editor->get_gizmo_target_center();
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
@@ -1471,6 +1468,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::LEFT: {
if (b->is_pressed()) {
+ clicked_wants_append = b->is_shift_pressed();
+
if (_edit.mode != TRANSFORM_NONE && _edit.instant) {
commit_transform();
break; // just commit the edit, stop processing the event so we don't deselect the object
@@ -1600,8 +1599,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
//clicking is always deferred to either move or release
- clicked_wants_append = b->is_shift_pressed();
-
if (clicked.is_null()) {
//default to regionselect
cursor.region_select = true;
@@ -1730,6 +1727,12 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.mode = TRANSFORM_TRANSLATE;
}
+ // enable region-select if nothing has been selected yet or multi-select (shift key) is active
+ if (movement_threshold_passed && (get_selected_count() == 0 || clicked_wants_append)) {
+ cursor.region_select = true;
+ cursor.region_begin = _edit.original_mouse_pos;
+ }
+
if (cursor.region_select) {
cursor.region_end = m->get_position();
surface->update();
@@ -3367,7 +3370,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
Transform3D xform = spatial_editor->get_gizmo_transform();
- const Transform3D camera_xform = camera->get_transform();
+ Transform3D camera_xform = camera->get_transform();
if (xform.origin.is_equal_approx(camera_xform.origin)) {
for (int i = 0; i < 3; i++) {
@@ -3386,63 +3389,11 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized();
const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized();
const Plane p = Plane(camz, camera_xform.origin);
- const real_t gizmo_d = CLAMP(Math::abs(p.distance_to(xform.origin)), camera->get_near() * 2, camera->get_far() / 2);
+ const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);
const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;
const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;
const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON);
- // This code ensures the gizmo stays on the screen. This includes if
- // the gizmo would otherwise be behind the camera, to the sides of
- // the camera, too close to the edge of the screen, too close to
- // the camera, or too far away from the camera. First we calculate
- // where the gizmo would go on screen, then we put it there.
- const Vector3 object_position = spatial_editor->get_gizmo_target_center();
- Vector2 gizmo_screen_position = camera->unproject_position(object_position);
- const Vector2 viewport_size = viewport->get_size();
- // We would use "camera.is_position_behind(parent_translation)" instead of dot,
- // except that it also accounts for the near clip plane, which we don't want.
- const bool is_in_front = camera_xform.basis.get_column(2).dot(object_position - camera_xform.origin) < 0;
- const bool is_in_viewport = is_in_front && Rect2(Vector2(0, 0), viewport_size).has_point(gizmo_screen_position);
- if (spatial_editor->is_keep_gizmo_onscreen_enabled()) {
- if (!spatial_editor->is_gizmo_visible() || is_in_viewport) {
- // In this case, the gizmo is either not visible, or in the viewport
- // already, so we should hide the offscreen line.
- gizmo_offscreen_line->hide();
- } else {
- // In this case, the point is not "normally" on screen, and
- // it should be placed in the center.
- const Vector2 half_viewport_size = viewport_size / 2;
- gizmo_screen_position = half_viewport_size;
- // The rest of this is for drawing the offscreen line.
- // One point goes in the center of the viewport.
- // Calculate where to put the other point of the line.
- Vector2 unprojected_position = camera->unproject_position(object_position);
- if (!is_in_front) {
- // When the object is behind, we need to flip and grow the line.
- unprojected_position -= half_viewport_size;
- unprojected_position = unprojected_position.normalized() * -half_viewport_size.length_squared();
- unprojected_position += half_viewport_size;
- }
- gizmo_offscreen_line->point1 = half_viewport_size;
- gizmo_offscreen_line->point2 = unprojected_position;
- gizmo_offscreen_line->update();
- gizmo_offscreen_line->show();
- }
- // Update the gizmo's position using what we calculated.
- xform.origin = camera->project_position(gizmo_screen_position, gizmo_d);
- } else {
- // In this case, the user does not want the gizmo to be
- // kept on screen, so we should hide the offscreen line,
- // and just use the gizmo's unmodified position.
- gizmo_offscreen_line->hide();
- if (is_in_viewport) {
- xform.origin = camera->project_position(gizmo_screen_position, gizmo_d);
- } else {
- xform.origin = object_position;
- }
- }
- spatial_editor->set_gizmo_transform(xform);
-
const real_t gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size");
// At low viewport heights, multiply the gizmo scale based on the viewport height.
// This prevents the gizmo from growing very large and going outside the viewport.
@@ -3451,6 +3402,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *
MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height /
subviewport_container->get_stretch_shrink();
+ Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;
// if the determinant is zero, we should disable the gizmo from being rendered
// this prevents supplying bad values to the renderer and then having to filter it out again
@@ -3472,7 +3424,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
if (xform.basis.get_axis(i).normalized().dot(xform.basis.get_axis((i + 1) % 3).normalized()) < 1.0) {
axis_angle = axis_angle.looking_at(xform.basis.get_axis(i).normalized(), xform.basis.get_axis((i + 1) % 3).normalized());
}
- axis_angle.basis *= gizmo_scale;
+ axis_angle.basis.scale(scale);
axis_angle.origin = xform.origin;
RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle);
RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));
@@ -3495,7 +3447,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
// Rotation white outline
xform.orthonormalize();
- xform.basis *= gizmo_scale;
+ xform.basis.scale(scale);
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
}
@@ -4077,7 +4029,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_
void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {
if (get_selected_count() > 0) {
_edit.mode = p_mode;
- _compute_edit(_edit.mouse_pos, false);
+ _compute_edit(_edit.mouse_pos);
_edit.instant = instant;
_edit.snap = spatial_editor->is_snap_enabled();
}
@@ -4480,14 +4432,15 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
zoom_indicator_delay = 0.0;
spatial_editor = p_spatial_editor;
- subviewport_container = memnew(SubViewportContainer);
- subviewport_container->set_stretch(true);
- add_child(subviewport_container);
- subviewport_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ SubViewportContainer *c = memnew(SubViewportContainer);
+ subviewport_container = c;
+ c->set_stretch(true);
+ add_child(c);
+ c->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
viewport = memnew(SubViewport);
viewport->set_disable_input(true);
- subviewport_container->add_child(viewport);
+ c->add_child(viewport);
surface = memnew(Control);
surface->set_drag_forwarding(this);
add_child(surface);
@@ -4500,9 +4453,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
camera->make_current();
surface->set_focus_mode(FOCUS_ALL);
- gizmo_offscreen_line = memnew(GizmoOffScreenLine);
- subviewport_container->add_child(gizmo_offscreen_line);
-
VBoxContainer *vbox = memnew(VBoxContainer);
surface->add_child(vbox);
vbox->set_offset(SIDE_LEFT, 10 * EDSCALE);
@@ -5127,7 +5077,6 @@ void Node3DEditor::update_transform_gizmo() {
gizmo.visible = count > 0;
gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();
gizmo.transform.basis = (count == 1) ? gizmo_basis : Basis();
- gizmo.target_center = gizmo_center / count;
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i]->update_transform_gizmo_view();
@@ -5280,7 +5229,6 @@ Dictionary Node3DEditor::get_state() const {
d["show_grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID));
d["show_origin"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN));
- d["keep_gizmo_onscreen"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN));
d["fov"] = get_fov();
d["znear"] = get_znear();
d["zfar"] = get_zfar();
@@ -5405,13 +5353,6 @@ void Node3DEditor::set_state(const Dictionary &p_state) {
RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);
}
}
- if (d.has("keep_gizmo_onscreen")) {
- bool use = d["keep_gizmo_onscreen"];
-
- if (use != view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN))) {
- _menu_item_pressed(MENU_KEEP_GIZMO_ONSCREEN);
- }
- }
if (d.has("gizmos_status")) {
Dictionary gizmos_status = d["gizmos_status"];
@@ -5765,13 +5706,7 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
_init_grid();
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), grid_enabled);
- } break;
- case MENU_KEEP_GIZMO_ONSCREEN: {
- keep_gizmo_onscreen = !view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(p_option));
- for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
- get_editor_viewport(i)->update_transform_gizmo_view();
- }
- view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), keep_gizmo_onscreen);
+
} break;
case MENU_VIEW_CAMERA_SETTINGS: {
settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));
@@ -7756,14 +7691,12 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/keep_gizmo_onscreen", TTR("Keep Gizmo On Screen"), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::G), MENU_KEEP_GIZMO_ONSCREEN);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS);
p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);
p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true);
- p->set_item_checked(p->get_item_index(MENU_KEEP_GIZMO_ONSCREEN), true);
p->connect("id_pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed));
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index f14f8b90b9..bbe5615570 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -87,19 +87,6 @@ public:
void set_viewport(Node3DEditorViewport *p_viewport);
};
-class GizmoOffScreenLine : public Control {
- GDCLASS(GizmoOffScreenLine, Control);
-
-public:
- Vector2 point1;
- Vector2 point2;
- void _notification(int p_what) {
- if (p_what == NOTIFICATION_DRAW && is_visible()) {
- draw_line(point1, point2, Color(0.0f, 0.75f, 0.75f), 2);
- }
- }
-};
-
class Node3DEditorViewport : public Control {
GDCLASS(Node3DEditorViewport, Control);
friend class Node3DEditor;
@@ -214,7 +201,6 @@ private:
CheckBox *preview_camera;
SubViewportContainer *subviewport_container;
- GizmoOffScreenLine *gizmo_offscreen_line;
MenuButton *view_menu;
PopupMenu *display_submenu;
@@ -251,7 +237,7 @@ private:
};
void _update_name();
- void _compute_edit(const Point2 &p_point, const bool p_auto_center = true);
+ void _compute_edit(const Point2 &p_point);
void _clear_selected();
void _select_clicked(bool p_allow_locked);
ObjectID _select_ray(const Point2 &p_pos);
@@ -521,35 +507,6 @@ class Node3DEditor : public VBoxContainer {
public:
static const unsigned int VIEWPORTS_COUNT = 4;
- enum MenuOption {
- MENU_GROUP_SELECTED,
- MENU_KEEP_GIZMO_ONSCREEN,
- MENU_LOCK_SELECTED,
- MENU_SNAP_TO_FLOOR,
- MENU_TOOL_LIST_SELECT,
- MENU_TOOL_LOCAL_COORDS,
- MENU_TOOL_MOVE,
- MENU_TOOL_OVERRIDE_CAMERA,
- MENU_TOOL_ROTATE,
- MENU_TOOL_SCALE,
- MENU_TOOL_SELECT,
- MENU_TOOL_USE_SNAP,
- MENU_TRANSFORM_CONFIGURE_SNAP,
- MENU_TRANSFORM_DIALOG,
- MENU_UNGROUP_SELECTED,
- MENU_UNLOCK_SELECTED,
- MENU_VIEW_CAMERA_SETTINGS,
- MENU_VIEW_GIZMOS_3D_ICONS,
- MENU_VIEW_GRID,
- MENU_VIEW_ORIGIN,
- MENU_VIEW_USE_1_VIEWPORT,
- MENU_VIEW_USE_2_VIEWPORTS,
- MENU_VIEW_USE_2_VIEWPORTS_ALT,
- MENU_VIEW_USE_3_VIEWPORTS,
- MENU_VIEW_USE_3_VIEWPORTS_ALT,
- MENU_VIEW_USE_4_VIEWPORTS,
- };
-
enum ToolMode {
TOOL_MODE_SELECT,
TOOL_MODE_MOVE,
@@ -568,6 +525,7 @@ public:
TOOL_OPT_USE_SNAP,
TOOL_OPT_OVERRIDE_CAMERA,
TOOL_OPT_MAX
+
};
private:
@@ -596,7 +554,6 @@ private:
Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE;
Vector3 grid_camera_last_update_position = Vector3();
- bool keep_gizmo_onscreen = true;
Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3];
Ref<StandardMaterial3D> gizmo_color[3];
Ref<StandardMaterial3D> plane_gizmo_color[3];
@@ -630,10 +587,37 @@ private:
struct Gizmo {
bool visible = false;
real_t scale = 0;
- Vector3 target_center;
Transform3D transform;
} gizmo;
+ enum MenuOption {
+ MENU_TOOL_SELECT,
+ MENU_TOOL_MOVE,
+ MENU_TOOL_ROTATE,
+ MENU_TOOL_SCALE,
+ MENU_TOOL_LIST_SELECT,
+ MENU_TOOL_LOCAL_COORDS,
+ MENU_TOOL_USE_SNAP,
+ MENU_TOOL_OVERRIDE_CAMERA,
+ MENU_TRANSFORM_CONFIGURE_SNAP,
+ MENU_TRANSFORM_DIALOG,
+ MENU_VIEW_USE_1_VIEWPORT,
+ MENU_VIEW_USE_2_VIEWPORTS,
+ MENU_VIEW_USE_2_VIEWPORTS_ALT,
+ MENU_VIEW_USE_3_VIEWPORTS,
+ MENU_VIEW_USE_3_VIEWPORTS_ALT,
+ MENU_VIEW_USE_4_VIEWPORTS,
+ MENU_VIEW_ORIGIN,
+ MENU_VIEW_GRID,
+ MENU_VIEW_GIZMOS_3D_ICONS,
+ MENU_VIEW_CAMERA_SETTINGS,
+ MENU_LOCK_SELECTED,
+ MENU_UNLOCK_SELECTED,
+ MENU_GROUP_SELECTED,
+ MENU_UNGROUP_SELECTED,
+ MENU_SNAP_TO_FLOOR
+ };
+
Button *tool_button[TOOL_MAX];
Button *tool_option_button[TOOL_OPT_MAX];
@@ -792,10 +776,7 @@ public:
float get_zfar() const { return settings_zfar->get_value(); }
float get_fov() const { return settings_fov->get_value(); }
- Vector3 get_gizmo_target_center() const { return gizmo.target_center; }
Transform3D get_gizmo_transform() const { return gizmo.transform; }
- void set_gizmo_transform(const Transform3D &p_transform) { gizmo.transform = p_transform; }
- bool is_keep_gizmo_onscreen_enabled() const { return keep_gizmo_onscreen; }
bool is_gizmo_visible() const;
ToolMode get_tool_mode() const { return tool_mode; }
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index cfb42c0741..57e47a15fd 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -40,7 +40,6 @@
#include "core/os/os.h"
#include "core/string/translation.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "editor/editor_vcs_interface.h"
#include "editor_scale.h"
#include "editor_settings.h"
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 0e362b13c6..a4f9563840 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1560,7 +1560,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_
for (int i = 0; i < anim->get_track_count(); i++) {
NodePath track_np = anim->track_get_path(i);
- Node *n = root->get_node(track_np);
+ Node *n = root->get_node_or_null(track_np);
if (!n) {
continue;
}
diff --git a/main/main.cpp b/main/main.cpp
index 216d0a446a..1efe3ccd94 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -50,7 +50,6 @@
#include "core/register_core_types.h"
#include "core/string/translation.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "drivers/register_driver_types.h"
#include "main/app_icon.gen.h"
#include "main/main_timer_sync.h"
@@ -200,7 +199,7 @@ static String unescape_cmdline(const String &p_str) {
static String get_full_version_string() {
String hash = String(VERSION_HASH);
- if (hash.length() != 0) {
+ if (!hash.is_empty()) {
hash = "." + hash.left(9);
}
return String(VERSION_FULL_BUILD) + hash;
@@ -2790,8 +2789,6 @@ void Main::cleanup(bool p_force) {
ERR_FAIL_COND(!_start_success);
}
- EngineDebugger::deinitialize();
-
ResourceLoader::remove_custom_loaders();
ResourceSaver::remove_custom_savers();
@@ -2834,6 +2831,8 @@ void Main::cleanup(bool p_force) {
unregister_scene_types();
unregister_server_types();
+ EngineDebugger::deinitialize();
+
if (xr_server) {
memdelete(xr_server);
}
diff --git a/methods.py b/methods.py
index fbd304ddde..fe84641e9d 100644
--- a/methods.py
+++ b/methods.py
@@ -111,10 +111,9 @@ def update_version(module_version_string=""):
f.close()
# NOTE: It is safe to generate this file here, since this is still executed serially
- fhash = open("core/version_hash.gen.h", "w")
+ fhash = open("core/version_hash.gen.cpp", "w")
fhash.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
- fhash.write("#ifndef VERSION_HASH_GEN_H\n")
- fhash.write("#define VERSION_HASH_GEN_H\n")
+ fhash.write('#include "core/version.h"\n')
githash = ""
gitfolder = ".git"
@@ -132,8 +131,7 @@ def update_version(module_version_string=""):
else:
githash = head
- fhash.write('#define VERSION_HASH "' + githash + '"\n')
- fhash.write("#endif // VERSION_HASH_GEN_H\n")
+ fhash.write('const char *const VERSION_HASH = "' + githash + '";\n')
fhash.close()
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index baa39a3b80..0056fee7a4 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -57,7 +57,6 @@
#include "core/variant/typed_array.h"
#include "core/variant/variant.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "drivers/png/png_driver_common.h"
#include "editor/import/resource_importer_scene.h"
#include "scene/2d/node_2d.h"
@@ -6655,8 +6654,8 @@ Error GLTFDocument::_serialize_version(Ref<GLTFState> state) {
Dictionary asset;
asset["version"] = version;
- String hash = VERSION_HASH;
- asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash);
+ String hash = String(VERSION_HASH);
+ asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);
state->json["asset"] = asset;
ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED);
ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED);
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
index 1c72a26b4a..fcb97fa63a 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -59,4 +59,9 @@ class String;
- (void)stopRendering;
- (void)startRendering;
+- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
+
@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
index b90c10fa84..ae92f32851 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -336,7 +336,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+- (void)godotTouchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
@@ -349,7 +349,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
@@ -363,7 +363,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
@@ -377,7 +377,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm
index b50ba5f942..a46c42765a 100644
--- a/platform/iphone/godot_view_gesture_recognizer.mm
+++ b/platform/iphone/godot_view_gesture_recognizer.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "godot_view_gesture_recognizer.h"
+#import "godot_view.h"
#include "core/config/project_settings.h"
@@ -58,6 +59,10 @@ const CGFloat kGLGestureMovementDistance = 0.5;
@implementation GodotViewGestureRecognizer
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
+
- (instancetype)init {
self = [super init];
@@ -104,7 +109,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
self.delayTimer = nil;
if (self.delayedTouches) {
- [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ [self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent];
}
self.delayedTouches = nil;
@@ -114,6 +119,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
[self delayTouches:cleared andEvent:event];
+
+ [super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
@@ -123,8 +130,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;
// We should check if movement was significant enough to fire an event
// for dragging to work correctly.
for (UITouch *touch in cleared) {
- CGPoint from = [touch locationInView:self.view];
- CGPoint to = [touch previousLocationInView:self.view];
+ CGPoint from = [touch locationInView:self.godotView];
+ CGPoint to = [touch previousLocationInView:self.godotView];
CGFloat xDistance = from.x - to.x;
CGFloat yDistance = from.y - to.y;
@@ -133,7 +140,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
// Early exit, since one of touches has moved enough to fire a drag event.
if (distance > kGLGestureMovementDistance) {
[self.delayTimer fire];
- [self.view touchesMoved:cleared withEvent:event];
+ [self.godotView godotTouchesMoved:cleared withEvent:event];
return;
}
}
@@ -141,26 +148,32 @@ const CGFloat kGLGestureMovementDistance = 0.5;
return;
}
- [self.view touchesMoved:cleared withEvent:event];
+ [self.godotView touchesMoved:cleared withEvent:event];
+
+ [super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
- [self.view touchesEnded:cleared withEvent:event];
+ [self.godotView godotTouchesEnded:cleared withEvent:event];
+
+ [super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
- [self.view touchesCancelled:touches withEvent:event];
-};
+ [self.godotView godotTouchesCancelled:touches withEvent:event];
+
+ [super touchesCancelled:touches withEvent:event];
+}
- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
NSMutableSet *cleared = [touches mutableCopy];
for (UITouch *touch in touches) {
- if (touch.phase != phaseToSave) {
+ if (touch.view != self.view || touch.phase != phaseToSave) {
[cleared removeObject:touch];
}
}
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index e9369fefdd..b4ec7924f6 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#ifdef DEBUG_ENABLED
@@ -71,10 +70,10 @@ static void handle_crash(int sig) {
}
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm
index 16be941308..3e640b3bf3 100644
--- a/platform/osx/crash_handler_osx.mm
+++ b/platform/osx/crash_handler_osx.mm
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#include <string.h>
@@ -94,10 +93,10 @@ static void handle_crash(int sig) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index 71e9d9acbd..5064f6b97f 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#ifdef CRASH_HANDLER_EXCEPTION
@@ -179,10 +178,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
}
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 41340f281b..e5b81f9d8d 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -31,30 +31,121 @@
#include "scene_debugger.h"
#include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
#include "core/io/marshalls.h"
#include "core/object/script_language.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
-void SceneDebugger::initialize() {
+Array SceneDebugger::RPCProfilerFrame::serialize() {
+ Array arr;
+ arr.push_back(infos.size() * 4);
+ for (int i = 0; i < infos.size(); ++i) {
+ arr.push_back(uint64_t(infos[i].node));
+ arr.push_back(infos[i].node_path);
+ arr.push_back(infos[i].incoming_rpc);
+ arr.push_back(infos[i].outgoing_rpc);
+ }
+ return arr;
+}
+
+bool SceneDebugger::RPCProfilerFrame::deserialize(const Array &p_arr) {
+ ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ uint32_t size = p_arr[0];
+ ERR_FAIL_COND_V(size % 4, false);
+ ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
+ infos.resize(size / 4);
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 4; ++i) {
+ infos.write[i].node = uint64_t(p_arr[idx]);
+ infos.write[i].node_path = p_arr[idx + 1];
+ infos.write[i].incoming_rpc = p_arr[idx + 2];
+ infos.write[i].outgoing_rpc = p_arr[idx + 3];
+ }
+ return true;
+}
+
+class SceneDebugger::RPCProfiler : public EngineProfiler {
+ Map<ObjectID, RPCNodeInfo> rpc_node_data;
+ uint64_t last_profile_time = 0;
+
+ void init_node(const ObjectID p_node) {
+ if (rpc_node_data.has(p_node)) {
+ return;
+ }
+ rpc_node_data.insert(p_node, RPCNodeInfo());
+ rpc_node_data[p_node].node = p_node;
+ rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
+ rpc_node_data[p_node].incoming_rpc = 0;
+ rpc_node_data[p_node].outgoing_rpc = 0;
+ }
+
+public:
+ void toggle(bool p_enable, const Array &p_opts) {
+ rpc_node_data.clear();
+ }
+
+ void add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() < 2);
+ const ObjectID id = p_data[0];
+ const String what = p_data[1];
+ init_node(id);
+ RPCNodeInfo &info = rpc_node_data[id];
+ if (what == "rpc_in") {
+ info.incoming_rpc++;
+ } else if (what == "rpc_out") {
+ info.outgoing_rpc++;
+ }
+ }
+
+ 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_profile_time > 100) {
+ last_profile_time = pt;
+ RPCProfilerFrame frame;
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) {
+ frame.infos.push_back(E.value);
+ }
+ rpc_node_data.clear();
+ EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
+ }
+ }
+};
+
+SceneDebugger *SceneDebugger::singleton = nullptr;
+
+SceneDebugger::SceneDebugger() {
+ singleton = this;
+ rpc_profiler.instantiate();
+ rpc_profiler->bind("rpc");
#ifdef DEBUG_ENABLED
LiveEditor::singleton = memnew(LiveEditor);
EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message));
#endif
}
-void SceneDebugger::deinitialize() {
+SceneDebugger::~SceneDebugger() {
#ifdef DEBUG_ENABLED
if (LiveEditor::singleton) {
- // Should be removed automatically when deiniting debugger, but just in case
- if (EngineDebugger::has_capture("scene")) {
- EngineDebugger::unregister_message_capture("scene");
- }
+ EngineDebugger::unregister_message_capture("scene");
memdelete(LiveEditor::singleton);
LiveEditor::singleton = nullptr;
}
#endif
+ singleton = nullptr;
+}
+
+void SceneDebugger::initialize() {
+ if (EngineDebugger::is_active()) {
+ memnew(SceneDebugger);
+ }
+}
+
+void SceneDebugger::deinitialize() {
+ if (singleton) {
+ memdelete(singleton);
+ }
}
#ifdef DEBUG_ENABLED
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index c8298391bb..dd0a17c2dc 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -32,6 +32,7 @@
#define SCENE_DEBUGGER_H
#include "core/object/class_db.h"
+#include "core/object/ref_counted.h"
#include "core/string/ustring.h"
#include "core/templates/pair.h"
#include "core/variant/array.h"
@@ -41,9 +42,36 @@ class Node;
class SceneDebugger {
public:
+ // RPC profiler
+ struct RPCNodeInfo {
+ ObjectID node;
+ String node_path;
+ int incoming_rpc = 0;
+ int outgoing_rpc = 0;
+ };
+
+ struct RPCProfilerFrame {
+ Vector<RPCNodeInfo> infos;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+private:
+ class RPCProfiler;
+
+ static SceneDebugger *singleton;
+
+ Ref<RPCProfiler> rpc_profiler;
+
+ SceneDebugger();
+
+public:
static void initialize();
static void deinitialize();
+ ~SceneDebugger();
+
#ifdef DEBUG_ENABLED
private:
static void _save_node(ObjectID id, const String &p_path);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index b26b9d1225..61a5fb999c 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -736,26 +736,7 @@ void PopupMenu::_notification(int p_what) {
}
} break;
case NOTIFICATION_THEME_CHANGED:
- case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
- // Pass the layout direction to all submenus.
- for (int i = 0; i < items.size(); i++) {
- if (items[i].submenu.is_empty()) {
- continue;
- }
-
- Node *n = get_node(items[i].submenu);
- if (!n) {
- continue;
- }
-
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- if (pm) {
- pm->set_layout_direction(get_layout_direction());
- }
- }
-
- [[fallthrough]];
- }
+ case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < items.size(); i++) {
items.write[i].xl_text = atr(items[i].text);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 9a6c87276f..a36eaaa0ee 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -905,6 +905,12 @@ String TreeItem::get_button_tooltip(int p_column, int p_idx) const {
return cells[p_column].buttons[p_idx].tooltip;
}
+int TreeItem::get_button_id(int p_column, int p_idx) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), -1);
+ ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), -1);
+ return cells[p_column].buttons[p_idx].id;
+}
+
void TreeItem::erase_button(int p_column, int p_idx) {
ERR_FAIL_INDEX(p_column, cells.size());
ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size());
@@ -1283,9 +1289,11 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_custom_as_button", "column", "enable"), &TreeItem::set_custom_as_button);
ClassDB::bind_method(D_METHOD("is_custom_set_as_button", "column"), &TreeItem::is_custom_set_as_button);
- ClassDB::bind_method(D_METHOD("add_button", "column", "button", "button_idx", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_button_count", "column"), &TreeItem::get_button_count);
ClassDB::bind_method(D_METHOD("get_button_tooltip", "column", "button_idx"), &TreeItem::get_button_tooltip);
+ ClassDB::bind_method(D_METHOD("get_button_id", "column", "button_idx"), &TreeItem::get_button_id);
+ ClassDB::bind_method(D_METHOD("get_button_by_id", "column", "id"), &TreeItem::get_button_by_id);
ClassDB::bind_method(D_METHOD("get_button", "column", "button_idx"), &TreeItem::get_button);
ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button);
ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button);
@@ -4856,6 +4864,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position);
ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position);
ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position);
+ ClassDB::bind_method(D_METHOD("get_button_id_at_position", "position"), &Tree::get_button_id_at_position);
ClassDB::bind_method(D_METHOD("ensure_cursor_is_visible"), &Tree::ensure_cursor_is_visible);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 255a4f0576..dc786de6dc 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -248,6 +248,7 @@ public:
int get_button_count(int p_column) const;
String get_button_tooltip(int p_column, int p_idx) const;
Ref<Texture2D> get_button(int p_column, int p_idx) const;
+ int get_button_id(int p_column, int p_idx) const;
void erase_button(int p_column, int p_idx);
int get_button_by_id(int p_column, int p_id) const;
void set_button(int p_column, int p_idx, const Ref<Texture2D> &p_button);
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 6b9d8ab211..d5bc7d111a 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -612,14 +612,15 @@ Variant Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallEr
return Variant();
}
- if (p_args[0]->get_type() != Variant::STRING_NAME) {
+ Variant::Type type = p_args[0]->get_type();
+ if (type != Variant::STRING_NAME && type != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
return Variant();
}
- StringName method = *p_args[0];
+ StringName method = (*p_args[0]).operator StringName();
rpcp(0, method, &p_args[1], p_argcount - 1);
@@ -641,7 +642,8 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal
return Variant();
}
- if (p_args[1]->get_type() != Variant::STRING_NAME) {
+ Variant::Type type = p_args[1]->get_type();
+ if (type != Variant::STRING_NAME && type != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::STRING_NAME;
@@ -649,7 +651,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal
}
int peer_id = *p_args[0];
- StringName method = *p_args[1];
+ StringName method = (*p_args[1]).operator StringName();
rpcp(peer_id, method, &p_args[2], p_argcount - 2);
diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp
index de4a94470a..2f278ed864 100644
--- a/scene/multiplayer/scene_cache_interface.cpp
+++ b/scene/multiplayer/scene_cache_interface.cpp
@@ -107,6 +107,10 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND(multiplayer_peer.is_null());
+#ifdef DEBUG_ENABLED
+ multiplayer->profile_bandwidth("out", packet.size());
+#endif
+
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
multiplayer_peer->set_target_peer(p_from);
@@ -188,6 +192,10 @@ bool SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Path
Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_V(multiplayer_peer.is_null(), false);
+#ifdef DEBUG_ENABLED
+ multiplayer->profile_bandwidth("out", packet.size() * peers_to_add.size());
+#endif
+
for (int &E : peers_to_add) {
multiplayer_peer->set_target_peer(E); // To all of you.
multiplayer_peer->set_transfer_channel(0);
diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp
index 2088a43ba7..25a704b37e 100644
--- a/scene/multiplayer/scene_replication_interface.cpp
+++ b/scene/multiplayer/scene_replication_interface.cpp
@@ -147,6 +147,11 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
+
+#ifdef DEBUG_ENABLED
+ multiplayer->profile_bandwidth("out", p_size);
+#endif
+
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
peer->set_target_peer(p_peer);
peer->set_transfer_channel(0);
diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp
index 7d7f57b9a1..2239a5d81c 100644
--- a/scene/multiplayer/scene_rpc_interface.cpp
+++ b/scene/multiplayer/scene_rpc_interface.cpp
@@ -46,12 +46,11 @@ void SceneRPCInterface::make_default() {
#ifdef DEBUG_ENABLED
_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {
- if (EngineDebugger::is_profiling("multiplayer")) {
+ if (EngineDebugger::is_profiling("rpc")) {
Array values;
- values.push_back("node");
values.push_back(p_id);
values.push_back(p_what);
- EngineDebugger::profiler_add_frame_data("multiplayer", values);
+ EngineDebugger::profiler_add_frame_data("rpc", values);
}
}
#else
@@ -268,7 +267,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
argp.resize(argc);
#ifdef DEBUG_ENABLED
- _profile_node_data("in_rpc", p_node->get_instance_id());
+ _profile_node_data("rpc_in", p_node->get_instance_id());
#endif
int out;
@@ -460,7 +459,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
uint16_t rpc_id = UINT16_MAX;
const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id);
ERR_FAIL_COND_MSG(config.name == StringName(),
- vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, node->get_path()));
+ vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is missing or not marked for RPCs in the local script.", p_method, node->get_path()));
if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) {
if (rpc_id & (1 << 15)) {
call_local_native = config.call_local;
@@ -471,7 +470,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
if (p_peer_id != node_id) {
#ifdef DEBUG_ENABLED
- _profile_node_data("out_rpc", node->get_instance_id());
+ _profile_node_data("rpc_out", node->get_instance_id());
#endif
_send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount);
diff --git a/servers/SCsub b/servers/SCsub
index 76c11724d3..2cd4741d56 100644
--- a/servers/SCsub
+++ b/servers/SCsub
@@ -12,6 +12,7 @@ SConscript("physics_2d/SCsub")
SConscript("rendering/SCsub")
SConscript("audio/SCsub")
SConscript("text/SCsub")
+SConscript("debugger/SCsub")
lib = env.add_library("servers", env.servers_sources)
diff --git a/servers/debugger/SCsub b/servers/debugger/SCsub
new file mode 100644
index 0000000000..86681f9c74
--- /dev/null
+++ b/servers/debugger/SCsub
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.servers_sources, "*.cpp")
diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp
new file mode 100644
index 0000000000..d1391937d9
--- /dev/null
+++ b/servers/debugger/servers_debugger.cpp
@@ -0,0 +1,463 @@
+/*************************************************************************/
+/* servers_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "servers_debugger.h"
+
+#include "core/config/project_settings.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
+#include "core/io/marshalls.h"
+#include "servers/display_server.h"
+
+#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
+#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
+
+Array ServersDebugger::ResourceUsage::serialize() {
+ infos.sort();
+
+ Array arr;
+ arr.push_back(infos.size() * 4);
+ for (const ResourceInfo &E : infos) {
+ arr.push_back(E.path);
+ arr.push_back(E.format);
+ arr.push_back(E.type);
+ arr.push_back(E.vram);
+ }
+ return arr;
+}
+
+bool ServersDebugger::ResourceUsage::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 1, "ResourceUsage");
+ uint32_t size = p_arr[0];
+ CHECK_SIZE(p_arr, size, "ResourceUsage");
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 4; i++) {
+ ResourceInfo info;
+ info.path = p_arr[idx];
+ info.format = p_arr[idx + 1];
+ info.type = p_arr[idx + 2];
+ info.vram = p_arr[idx + 3];
+ infos.push_back(info);
+ }
+ CHECK_END(p_arr, idx, "ResourceUsage");
+ return true;
+}
+
+Array ServersDebugger::ScriptFunctionSignature::serialize() {
+ Array arr;
+ arr.push_back(name);
+ arr.push_back(id);
+ return arr;
+}
+
+bool ServersDebugger::ScriptFunctionSignature::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature");
+ name = p_arr[0];
+ id = p_arr[1];
+ CHECK_END(p_arr, 2, "ScriptFunctionSignature");
+ return true;
+}
+
+Array ServersDebugger::ServersProfilerFrame::serialize() {
+ Array arr;
+ arr.push_back(frame_number);
+ arr.push_back(frame_time);
+ arr.push_back(idle_time);
+ arr.push_back(physics_time);
+ arr.push_back(physics_frame_time);
+ arr.push_back(script_time);
+
+ arr.push_back(servers.size());
+ for (int i = 0; i < servers.size(); i++) {
+ ServerInfo &s = servers[i];
+ arr.push_back(s.name);
+ arr.push_back(s.functions.size() * 2);
+ for (int j = 0; j < s.functions.size(); j++) {
+ ServerFunctionInfo &f = s.functions[j];
+ arr.push_back(f.name);
+ arr.push_back(f.time);
+ }
+ }
+
+ arr.push_back(script_functions.size() * 4);
+ for (int i = 0; i < script_functions.size(); i++) {
+ arr.push_back(script_functions[i].sig_id);
+ arr.push_back(script_functions[i].call_count);
+ arr.push_back(script_functions[i].self_time);
+ arr.push_back(script_functions[i].total_time);
+ }
+ return arr;
+}
+
+bool ServersDebugger::ServersProfilerFrame::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 7, "ServersProfilerFrame");
+ frame_number = p_arr[0];
+ frame_time = p_arr[1];
+ idle_time = p_arr[2];
+ physics_time = p_arr[3];
+ physics_frame_time = p_arr[4];
+ script_time = p_arr[5];
+ int servers_size = p_arr[6];
+ int idx = 7;
+ while (servers_size) {
+ CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame");
+ servers_size--;
+ ServerInfo si;
+ si.name = p_arr[idx];
+ int sub_data_size = p_arr[idx + 1];
+ idx += 2;
+ CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame");
+ for (int j = 0; j < sub_data_size / 2; j++) {
+ ServerFunctionInfo sf;
+ sf.name = p_arr[idx];
+ sf.time = p_arr[idx + 1];
+ idx += 2;
+ si.functions.push_back(sf);
+ }
+ servers.push_back(si);
+ }
+ CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame");
+ int func_size = p_arr[idx];
+ idx += 1;
+ CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame");
+ for (int i = 0; i < func_size / 4; i++) {
+ ScriptFunctionInfo fi;
+ fi.sig_id = p_arr[idx];
+ fi.call_count = p_arr[idx + 1];
+ fi.self_time = p_arr[idx + 2];
+ fi.total_time = p_arr[idx + 3];
+ script_functions.push_back(fi);
+ idx += 4;
+ }
+ CHECK_END(p_arr, idx, "ServersProfilerFrame");
+ return true;
+}
+
+Array ServersDebugger::VisualProfilerFrame::serialize() {
+ Array arr;
+ arr.push_back(frame_number);
+ arr.push_back(areas.size() * 3);
+ for (int i = 0; i < areas.size(); i++) {
+ arr.push_back(areas[i].name);
+ arr.push_back(areas[i].cpu_msec);
+ arr.push_back(areas[i].gpu_msec);
+ }
+ return arr;
+}
+
+bool ServersDebugger::VisualProfilerFrame::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 2, "VisualProfilerFrame");
+ frame_number = p_arr[0];
+ int size = p_arr[1];
+ CHECK_SIZE(p_arr, size, "VisualProfilerFrame");
+ int idx = 2;
+ areas.resize(size / 3);
+ RS::FrameProfileArea *w = areas.ptrw();
+ for (int i = 0; i < size / 3; i++) {
+ w[i].name = p_arr[idx];
+ w[i].cpu_msec = p_arr[idx + 1];
+ w[i].gpu_msec = p_arr[idx + 2];
+ idx += 3;
+ }
+ CHECK_END(p_arr, idx, "VisualProfilerFrame");
+ return true;
+}
+class ServersDebugger::ScriptsProfiler : public EngineProfiler {
+ typedef ServersDebugger::ScriptFunctionSignature FunctionSignature;
+ typedef ServersDebugger::ScriptFunctionInfo FunctionInfo;
+ struct ProfileInfoSort {
+ bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
+ return A->total_time < B->total_time;
+ }
+ };
+ Vector<ScriptLanguage::ProfilingInfo> info;
+ Vector<ScriptLanguage::ProfilingInfo *> ptrs;
+ Map<StringName, int> sig_map;
+ int max_frame_functions = 16;
+
+public:
+ void toggle(bool p_enable, const Array &p_opts) {
+ if (p_enable) {
+ sig_map.clear();
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->profiling_start();
+ }
+ if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) {
+ max_frame_functions = MAX(0, int(p_opts[0]));
+ }
+ } else {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->profiling_stop();
+ }
+ }
+ }
+
+ void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) {
+ int ofs = 0;
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ if (p_accumulated) {
+ ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs);
+ } else {
+ ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs);
+ }
+ }
+
+ for (int i = 0; i < ofs; i++) {
+ ptrs.write[i] = &info.write[i];
+ }
+
+ SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa;
+ sa.sort(ptrs.ptrw(), ofs);
+
+ int to_send = MIN(ofs, max_frame_functions);
+
+ // Check signatures first, and compute total time.
+ r_total = 0;
+ for (int i = 0; i < to_send; i++) {
+ if (!sig_map.has(ptrs[i]->signature)) {
+ int idx = sig_map.size();
+ FunctionSignature sig;
+ sig.name = ptrs[i]->signature;
+ sig.id = idx;
+ EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize());
+ sig_map[ptrs[i]->signature] = idx;
+ }
+ r_total += ptrs[i]->self_time;
+ }
+
+ // Send frame, script time, functions information then
+ r_funcs.resize(to_send);
+
+ FunctionInfo *w = r_funcs.ptrw();
+ for (int i = 0; i < to_send; i++) {
+ if (sig_map.has(ptrs[i]->signature)) {
+ w[i].sig_id = sig_map[ptrs[i]->signature];
+ }
+ w[i].call_count = ptrs[i]->call_count;
+ w[i].total_time = ptrs[i]->total_time / 1000000.0;
+ w[i].self_time = ptrs[i]->self_time / 1000000.0;
+ }
+ }
+
+ ScriptsProfiler() {
+ info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
+ ptrs.resize(info.size());
+ }
+};
+
+class ServersDebugger::ServersProfiler : public EngineProfiler {
+ bool skip_profile_frame = false;
+ typedef ServersDebugger::ServerInfo ServerInfo;
+ typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo;
+
+ Map<StringName, ServerInfo> server_data;
+ ScriptsProfiler scripts_profiler;
+
+ double frame_time = 0;
+ double idle_time = 0;
+ double physics_time = 0;
+ double physics_frame_time = 0;
+
+ void _send_frame_data(bool p_final) {
+ ServersDebugger::ServersProfilerFrame frame;
+ frame.frame_number = Engine::get_singleton()->get_process_frames();
+ frame.frame_time = frame_time;
+ frame.idle_time = idle_time;
+ frame.physics_time = physics_time;
+ frame.physics_frame_time = physics_frame_time;
+ Map<StringName, ServerInfo>::Element *E = server_data.front();
+ while (E) {
+ if (!p_final) {
+ frame.servers.push_back(E->get());
+ }
+ E->get().functions.clear();
+ E = E->next();
+ }
+ uint64_t time = 0;
+ scripts_profiler.write_frame_data(frame.script_functions, time, p_final);
+ frame.script_time = USEC_TO_SEC(time);
+ if (skip_profile_frame) {
+ skip_profile_frame = false;
+ return;
+ }
+ if (p_final) {
+ EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize());
+ } else {
+ EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize());
+ }
+ }
+
+public:
+ void toggle(bool p_enable, const Array &p_opts) {
+ skip_profile_frame = false;
+ if (p_enable) {
+ server_data.clear(); // Clear old profiling data.
+ } else {
+ _send_frame_data(true); // Send final frame.
+ }
+ scripts_profiler.toggle(p_enable, p_opts);
+ }
+
+ void add(const Array &p_data) {
+ String name = p_data[0];
+ if (!server_data.has(name)) {
+ ServerInfo info;
+ info.name = name;
+ server_data[name] = info;
+ }
+ ServerInfo &srv = server_data[name];
+
+ ServerFunctionInfo fi;
+ fi.name = p_data[1];
+ fi.time = p_data[2];
+ srv.functions.push_back(fi);
+ }
+
+ 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;
+ physics_frame_time = p_physics_frame_time;
+ _send_frame_data(false);
+ }
+
+ void skip_frame() {
+ skip_profile_frame = true;
+ }
+};
+
+class ServersDebugger::VisualProfiler : public EngineProfiler {
+ typedef ServersDebugger::ServerInfo ServerInfo;
+ typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo;
+
+ Map<StringName, ServerInfo> server_data;
+
+public:
+ void toggle(bool p_enable, const Array &p_opts) {
+ RS::get_singleton()->set_frame_profiling_enabled(p_enable);
+ }
+
+ void add(const Array &p_data) {}
+
+ 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();
+ ServersDebugger::VisualProfilerFrame frame;
+ if (!profile_areas.size()) {
+ return;
+ }
+
+ frame.frame_number = RS::get_singleton()->get_frame_profile_frame();
+ frame.areas.append_array(profile_areas);
+ EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize());
+ }
+};
+
+ServersDebugger *ServersDebugger::singleton = nullptr;
+
+void ServersDebugger::initialize() {
+ if (EngineDebugger::is_active()) {
+ memnew(ServersDebugger);
+ }
+}
+
+void ServersDebugger::deinitialize() {
+ if (singleton) {
+ memdelete(singleton);
+ }
+}
+
+Error ServersDebugger::_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
+ ERR_FAIL_COND_V(!singleton, ERR_BUG);
+ r_captured = true;
+ if (p_cmd == "memory") {
+ singleton->_send_resource_usage();
+ } else if (p_cmd == "draw") { // Forced redraw.
+ // For camera override to stay live when the game is paused from the editor.
+ double delta = 0.0;
+ if (singleton->last_draw_time) {
+ delta = (OS::get_singleton()->get_ticks_usec() - singleton->last_draw_time) / 1000000.0;
+ }
+ singleton->last_draw_time = OS::get_singleton()->get_ticks_usec();
+ RenderingServer::get_singleton()->sync();
+ if (RenderingServer::get_singleton()->has_changed()) {
+ RenderingServer::get_singleton()->draw(true, delta);
+ }
+ } else if (p_cmd == "foreground") {
+ singleton->last_draw_time = 0.0;
+ DisplayServer::get_singleton()->window_move_to_foreground();
+ singleton->servers_profiler->skip_frame();
+ } else {
+ r_captured = false;
+ }
+ return OK;
+}
+
+void ServersDebugger::_send_resource_usage() {
+ ServersDebugger::ResourceUsage usage;
+
+ List<RS::TextureInfo> tinfo;
+ RS::get_singleton()->texture_debug_usage(&tinfo);
+
+ for (const RS::TextureInfo &E : tinfo) {
+ ServersDebugger::ResourceInfo info;
+ info.path = E.path;
+ info.vram = E.bytes;
+ info.id = E.texture;
+ info.type = "Texture";
+ if (E.depth == 0) {
+ info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format);
+ } else {
+ info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format);
+ }
+ usage.infos.push_back(info);
+ }
+
+ EngineDebugger::get_singleton()->send_message("servers:memory_usage", usage.serialize());
+}
+
+ServersDebugger::ServersDebugger() {
+ singleton = this;
+
+ // Generic servers profiler (audio/physics/...)
+ servers_profiler.instantiate();
+ servers_profiler->bind("servers");
+
+ // Visual Profiler (cpu/gpu times)
+ visual_profiler.instantiate();
+ visual_profiler->bind("visual");
+
+ EngineDebugger::Capture servers_cap(nullptr, &_capture);
+ EngineDebugger::register_message_capture("servers", servers_cap);
+}
+
+ServersDebugger::~ServersDebugger() {
+ EngineDebugger::unregister_message_capture("servers");
+ singleton = nullptr;
+}
diff --git a/servers/debugger/servers_debugger.h b/servers/debugger/servers_debugger.h
new file mode 100644
index 0000000000..d1c55dc690
--- /dev/null
+++ b/servers/debugger/servers_debugger.h
@@ -0,0 +1,132 @@
+/*************************************************************************/
+/* servers_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SERVER_DEBUGGER_H
+#define SERVER_DEBUGGER_H
+
+#include "core/debugger/debugger_marshalls.h"
+
+#include "servers/rendering_server.h"
+
+class ServersDebugger {
+public:
+ // Memory usage
+ struct ResourceInfo {
+ String path;
+ String format;
+ String type;
+ RID id;
+ int vram = 0;
+ bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
+ };
+
+ struct ResourceUsage {
+ List<ResourceInfo> infos;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ // Script Profiler
+ struct ScriptFunctionSignature {
+ StringName name;
+ int id = -1;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ struct ScriptFunctionInfo {
+ StringName name;
+ int sig_id = -1;
+ int call_count = 0;
+ double self_time = 0;
+ double total_time = 0;
+ };
+
+ // Servers profiler
+ struct ServerFunctionInfo {
+ StringName name;
+ double time = 0;
+ };
+
+ struct ServerInfo {
+ StringName name;
+ List<ServerFunctionInfo> functions;
+ };
+
+ struct ServersProfilerFrame {
+ int frame_number = 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;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ // Visual Profiler
+ struct VisualProfilerFrame {
+ uint64_t frame_number = 0;
+ Vector<RS::FrameProfileArea> areas;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+private:
+ class ScriptsProfiler;
+ class ServersProfiler;
+ class VisualProfiler;
+
+ double last_draw_time = 0.0;
+ Ref<ServersProfiler> servers_profiler;
+ Ref<VisualProfiler> visual_profiler;
+
+ static ServersDebugger *singleton;
+
+ static Error _capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);
+
+ void _send_resource_usage();
+
+ ServersDebugger();
+
+public:
+ static void initialize();
+ static void deinitialize();
+
+ ~ServersDebugger();
+};
+
+#endif // SERVERS_DEBUGGER_H
diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp
index 3726bcde35..f405dea770 100644
--- a/servers/register_server_types.cpp
+++ b/servers/register_server_types.cpp
@@ -56,6 +56,7 @@
#include "camera/camera_feed.h"
#include "camera_server.h"
#include "core/extension/native_extension_manager.h"
+#include "debugger/servers_debugger.h"
#include "display_server.h"
#include "navigation_server_2d.h"
#include "navigation_server_3d.h"
@@ -223,6 +224,8 @@ void register_server_types() {
GDREGISTER_CLASS(PhysicsTestMotionParameters3D);
GDREGISTER_CLASS(PhysicsTestMotionResult3D);
+ ServersDebugger::initialize();
+
// Physics 2D
GLOBAL_DEF(PhysicsServer2DManager::setting_property_name, "DEFAULT");
ProjectSettings::get_singleton()->set_custom_property_info(PhysicsServer2DManager::setting_property_name, PropertyInfo(Variant::STRING, PhysicsServer2DManager::setting_property_name, PROPERTY_HINT_ENUM, "DEFAULT"));
@@ -241,6 +244,8 @@ void register_server_types() {
}
void unregister_server_types() {
+ ServersDebugger::deinitialize();
+
NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS);
memdelete(shader_types);
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index ead196b7dd..91201b2028 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -7871,7 +7871,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (is_sampler_type(type)) {
if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE) {
- _set_error(vformat(RTR("Uniforms with '%s' qualifiers can't be of sampler type.", "instance")));
+ _set_error(vformat(RTR("The '%s' qualifier is not supported for sampler types."), "SCOPE_INSTANCE"));
return ERR_PARSE_ERROR;
}
uniform2.texture_order = texture_uniforms++;
@@ -7887,7 +7887,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
}
} else {
if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE && (type == TYPE_MAT2 || type == TYPE_MAT3 || type == TYPE_MAT4)) {
- _set_error(vformat(RTR("Uniforms with '%s' qualifier can't be of matrix type.", "instance")));
+ _set_error(vformat(RTR("The '%s' qualifier is not supported for matrix types."), "SCOPE_INSTANCE"));
return ERR_PARSE_ERROR;
}
uniform2.texture_order = -1;
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 34c33c3b56..f467d6a64b 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -206,7 +206,7 @@ Files extracted from upstream source:
## harfbuzz
- Upstream: https://github.com/harfbuzz/harfbuzz
-- Version: 3.3.1 (45df259538c204540819d74456d30ffb40df488a, 2022)
+- Version: 3.3.2 (ac46c3248e8b0316235943175c4d4a11c24dd4a9, 2022)
- License: MIT
Files extracted from upstream source:
@@ -465,7 +465,7 @@ Collection of single-file libraries used in Godot components.
## msdfgen
- Upstream: https://github.com/Chlumsky/msdfgen
-- Version: 1.9.1 (1b3b6b985094e6f12751177490add3ad11dd91a9, 2010)
+- Version: 1.9.2 (64a91eec3ca3787e6f78b4c99fcd3052ad3e37c0, 2021)
- License: MIT
Files extracted from the upstream source:
diff --git a/thirdparty/harfbuzz/src/hb-buffer.hh b/thirdparty/harfbuzz/src/hb-buffer.hh
index adf4aa2b6f..ac45f090a5 100644
--- a/thirdparty/harfbuzz/src/hb-buffer.hh
+++ b/thirdparty/harfbuzz/src/hb-buffer.hh
@@ -386,11 +386,14 @@ struct hb_buffer_t
HB_INTERNAL void delete_glyph ();
- void set_glyph_flags (hb_mask_t mask,
- unsigned start = 0,
- unsigned end = (unsigned) -1,
- bool interior = false,
- bool from_out_buffer = false)
+ /* Adds glyph flags in mask to infos with clusters between start and end.
+ * The start index will be from out-buffer if from_out_buffer is true.
+ * If interior is true, then the cluster having the minimum value is skipped. */
+ void _set_glyph_flags (hb_mask_t mask,
+ unsigned start = 0,
+ unsigned end = (unsigned) -1,
+ bool interior = false,
+ bool from_out_buffer = false)
{
end = hb_min (end, len);
@@ -437,27 +440,27 @@ struct hb_buffer_t
void unsafe_to_break (unsigned int start = 0, unsigned int end = -1)
{
- set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
- start, end,
- true);
+ _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
+ start, end,
+ true);
}
void unsafe_to_concat (unsigned int start = 0, unsigned int end = -1)
{
- set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
- start, end,
- true);
+ _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
+ start, end,
+ true);
}
void unsafe_to_break_from_outbuffer (unsigned int start = 0, unsigned int end = -1)
{
- set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
- start, end,
- true, true);
+ _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
+ start, end,
+ true, true);
}
void unsafe_to_concat_from_outbuffer (unsigned int start = 0, unsigned int end = -1)
{
- set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
- start, end,
- false, true);
+ _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT,
+ start, end,
+ false, true);
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh b/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh
index 9bac30fff3..87a7d800c1 100644
--- a/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh
@@ -917,7 +917,7 @@ struct glyf
struct accelerator_t
{
- accelerator_t (hb_face_t *face_)
+ accelerator_t (hb_face_t *face)
{
short_offset = false;
num_glyphs = 0;
@@ -930,7 +930,6 @@ struct glyf
#ifndef HB_NO_VERTICAL
vmtx = nullptr;
#endif
- face = face_;
const OT::head &head = *face->table.head;
if (head.indexToLocFormat > 1 || head.glyphDataFormat > 0)
/* Unknown format. Leave num_glyphs=0, that takes care of disabling us. */
@@ -1287,7 +1286,6 @@ struct glyf
unsigned int num_glyphs;
hb_blob_ptr_t<loca> loca_table;
hb_blob_ptr_t<glyf> glyf_table;
- hb_face_t *face;
};
struct SubsetGlyph
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh b/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh
index e28c951f3f..2f9186a2a7 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh
@@ -1586,7 +1586,15 @@ struct PairPosFormat2
/* Isolate simple kerning and apply it half to each side.
- * Results in better cursor positinoing / underline drawing. */
+ * Results in better cursor positinoing / underline drawing.
+ *
+ * Disabled, because causes issues... :-(
+ * https://github.com/harfbuzz/harfbuzz/issues/3408
+ * https://github.com/harfbuzz/harfbuzz/pull/3235#issuecomment-1029814978
+ */
+#ifndef HB_SPLIT_KERN
+ if (0)
+#endif
{
if (!len2)
{
diff --git a/thirdparty/harfbuzz/src/hb-version.h b/thirdparty/harfbuzz/src/hb-version.h
index 91ccb3dcde..493a09f8cf 100644
--- a/thirdparty/harfbuzz/src/hb-version.h
+++ b/thirdparty/harfbuzz/src/hb-version.h
@@ -53,14 +53,14 @@ HB_BEGIN_DECLS
*
* The micro component of the library version available at compile-time.
*/
-#define HB_VERSION_MICRO 1
+#define HB_VERSION_MICRO 2
/**
* HB_VERSION_STRING:
*
* A string literal containing the library version available at compile-time.
*/
-#define HB_VERSION_STRING "3.3.1"
+#define HB_VERSION_STRING "3.3.2"
/**
* HB_VERSION_ATLEAST:
diff --git a/thirdparty/harfbuzz/src/hb.hh b/thirdparty/harfbuzz/src/hb.hh
index 1f14267525..b9f5f71415 100644
--- a/thirdparty/harfbuzz/src/hb.hh
+++ b/thirdparty/harfbuzz/src/hb.hh
@@ -447,6 +447,7 @@ static int HB_UNUSED _hb_errno = 0;
#ifndef HB_USE_ATEXIT
# define HB_USE_ATEXIT 0
#endif
+#ifndef hb_atexit
#if !HB_USE_ATEXIT
# define hb_atexit(_) HB_STMT_START { if (0) (_) (); } HB_STMT_END
#else /* HB_USE_ATEXIT */
@@ -457,6 +458,7 @@ static int HB_UNUSED _hb_errno = 0;
# define hb_atexit(f) static hb_atexit_t<f> _hb_atexit_##__LINE__;
# endif
#endif
+#endif
/* Lets assert int types. Saves trouble down the road. */
static_assert ((sizeof (hb_codepoint_t) == 4), "");
diff --git a/thirdparty/msdfgen/core/edge-coloring.cpp b/thirdparty/msdfgen/core/edge-coloring.cpp
index 370f9aa38d..914f1769fd 100644
--- a/thirdparty/msdfgen/core/edge-coloring.cpp
+++ b/thirdparty/msdfgen/core/edge-coloring.cpp
@@ -473,7 +473,7 @@ void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long l
edgeMatrix[i] = &edgeMatrixStorage[i*splineCount];
int nextEdge = 0;
for (; nextEdge < graphEdgeCount && !*graphEdgeDistances[nextEdge]; ++nextEdge) {
- int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase;
+ int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);
int row = elem/splineCount;
int col = elem%splineCount;
edgeMatrix[row][col] = 1;
@@ -483,7 +483,7 @@ void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long l
std::vector<int> coloring(2*splineCount);
colorSecondDegreeGraph(&coloring[0], &edgeMatrix[0], splineCount, seed);
for (; nextEdge < graphEdgeCount; ++nextEdge) {
- int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase;
+ int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);
tryAddEdge(&coloring[0], &edgeMatrix[0], splineCount, elem/splineCount, elem%splineCount, &coloring[splineCount]);
}
diff --git a/thirdparty/msdfgen/core/equation-solver.cpp b/thirdparty/msdfgen/core/equation-solver.cpp
index fbe906428b..4144fa3340 100644
--- a/thirdparty/msdfgen/core/equation-solver.cpp
+++ b/thirdparty/msdfgen/core/equation-solver.cpp
@@ -4,17 +4,15 @@
#define _USE_MATH_DEFINES
#include <cmath>
-#define TOO_LARGE_RATIO 1e12
-
namespace msdfgen {
int solveQuadratic(double x[2], double a, double b, double c) {
- // a = 0 -> linear equation
- if (a == 0 || fabs(b)+fabs(c) > TOO_LARGE_RATIO*fabs(a)) {
- // a, b = 0 -> no solution
- if (b == 0 || fabs(c) > TOO_LARGE_RATIO*fabs(b)) {
+ // a == 0 -> linear equation
+ if (a == 0 || fabs(b) > 1e12*fabs(a)) {
+ // a == 0, b == 0 -> no solution
+ if (b == 0) {
if (c == 0)
- return -1; // 0 = 0
+ return -1; // 0 == 0
return 0;
}
x[0] = -c/b;
@@ -35,41 +33,38 @@ int solveQuadratic(double x[2], double a, double b, double c) {
static int solveCubicNormed(double x[3], double a, double b, double c) {
double a2 = a*a;
- double q = (a2 - 3*b)/9;
- double r = (a*(2*a2-9*b) + 27*c)/54;
+ double q = 1/9.*(a2-3*b);
+ double r = 1/54.*(a*(2*a2-9*b)+27*c);
double r2 = r*r;
double q3 = q*q*q;
- double A, B;
+ a *= 1/3.;
if (r2 < q3) {
double t = r/sqrt(q3);
if (t < -1) t = -1;
if (t > 1) t = 1;
t = acos(t);
- a /= 3; q = -2*sqrt(q);
- x[0] = q*cos(t/3)-a;
- x[1] = q*cos((t+2*M_PI)/3)-a;
- x[2] = q*cos((t-2*M_PI)/3)-a;
+ q = -2*sqrt(q);
+ x[0] = q*cos(1/3.*t)-a;
+ x[1] = q*cos(1/3.*(t+2*M_PI))-a;
+ x[2] = q*cos(1/3.*(t-2*M_PI))-a;
return 3;
} else {
- A = -pow(fabs(r)+sqrt(r2-q3), 1/3.);
- if (r < 0) A = -A;
- B = A == 0 ? 0 : q/A;
- a /= 3;
- x[0] = (A+B)-a;
- x[1] = -0.5*(A+B)-a;
- x[2] = 0.5*sqrt(3.)*(A-B);
- if (fabs(x[2]) < 1e-14)
+ double u = (r < 0 ? 1 : -1)*pow(fabs(r)+sqrt(r2-q3), 1/3.);
+ double v = u == 0 ? 0 : q/u;
+ x[0] = (u+v)-a;
+ if (u == v || fabs(u-v) < 1e-12*fabs(u+v)) {
+ x[1] = -.5*(u+v)-a;
return 2;
+ }
return 1;
}
}
int solveCubic(double x[3], double a, double b, double c, double d) {
if (a != 0) {
- double bn = b/a, cn = c/a, dn = d/a;
- // Check that a isn't "almost zero"
- if (fabs(bn) < TOO_LARGE_RATIO && fabs(cn) < TOO_LARGE_RATIO && fabs(dn) < TOO_LARGE_RATIO)
- return solveCubicNormed(x, bn, cn, dn);
+ double bn = b/a;
+ if (fabs(bn) < 1e6) // Above this ratio, the numerical error gets larger than if we treated a as zero
+ return solveCubicNormed(x, bn, c/a, d/a);
}
return solveQuadratic(x, b, c, d);
}