summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/CODEOWNERS4
-rw-r--r--README.md1
-rw-r--r--SConstruct2
-rw-r--r--core/core_bind.cpp11
-rw-r--r--core/core_bind.h3
-rw-r--r--core/debugger/remote_debugger_peer.cpp8
-rw-r--r--core/debugger/remote_debugger_peer.h8
-rw-r--r--core/input/godotcontrollerdb.txt1
-rw-r--r--core/math/a_star_grid_2d.cpp24
-rw-r--r--core/math/a_star_grid_2d.h2
-rw-r--r--core/math/bvh_abb.h8
-rw-r--r--core/math/vector2.h4
-rw-r--r--core/math/vector3.cpp10
-rw-r--r--core/math/vector3.h7
-rw-r--r--core/math/vector3i.cpp10
-rw-r--r--core/math/vector3i.h3
-rw-r--r--core/math/vector4.cpp10
-rw-r--r--core/math/vector4.h9
-rw-r--r--core/math/vector4i.cpp10
-rw-r--r--core/math/vector4i.h3
-rw-r--r--core/os/mutex.cpp4
-rw-r--r--core/os/mutex.h27
-rw-r--r--core/os/os.cpp8
-rw-r--r--core/os/os.h2
-rw-r--r--core/os/rw_lock.h17
-rw-r--r--core/os/semaphore.h13
-rw-r--r--core/os/thread.cpp4
-rw-r--r--core/os/thread.h21
-rw-r--r--core/os/threaded_array_processor.h19
-rw-r--r--core/string/ustring.cpp16
-rw-r--r--core/templates/cowdata.h2
-rw-r--r--core/templates/safe_list.h159
-rw-r--r--core/templates/safe_refcount.h139
-rw-r--r--doc/classes/AStarGrid2D.xml2
-rw-r--r--doc/classes/Basis.xml2
-rw-r--r--doc/classes/FileAccess.xml10
-rw-r--r--doc/classes/MenuButton.xml6
-rw-r--r--doc/classes/OS.xml14
-rw-r--r--doc/classes/OptionButton.xml6
-rw-r--r--drivers/unix/os_unix.cpp2
-rw-r--r--drivers/unix/thread_posix.cpp4
-rw-r--r--drivers/unix/thread_posix.h2
-rw-r--r--editor/editor_inspector.cpp22
-rw-r--r--editor/editor_inspector.h4
-rw-r--r--editor/editor_resource_picker.cpp2
-rw-r--r--editor/editor_spin_slider.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp17
-rw-r--r--editor/plugins/script_editor_plugin.h1
-rw-r--r--editor/plugins/script_text_editor.cpp1
-rw-r--r--editor/plugins/shader_editor_plugin.cpp1174
-rw-r--r--editor/plugins/shader_editor_plugin.h181
-rw-r--r--editor/plugins/text_editor.cpp1
-rw-r--r--editor/plugins/text_shader_editor.cpp1191
-rw-r--r--editor/plugins/text_shader_editor.h199
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp54
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp8
-rw-r--r--editor/plugins/tiles/tile_set_editor.h2
-rw-r--r--main/main.cpp6
-rw-r--r--methods.py1
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp61
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs348
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs2
-rw-r--r--modules/mono/glue/runtime_interop.cpp5
-rw-r--r--modules/mono/managed_callable.cpp6
-rw-r--r--modules/mono/managed_callable.h3
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp46
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.h15
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp438
-rw-r--r--modules/multiplayer/scene_replication_interface.h75
-rw-r--r--modules/multiplayer/scene_replication_state.cpp267
-rw-r--r--modules/multiplayer/scene_replication_state.h135
-rw-r--r--modules/navigation/nav_map.cpp1
-rw-r--r--modules/noise/noise_texture_2d.cpp3
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.h1
-rw-r--r--platform/linuxbsd/display_server_x11.cpp5
-rw-r--r--platform/web/audio_driver_web.cpp46
-rw-r--r--platform/web/audio_driver_web.h41
-rw-r--r--platform/web/os_web.cpp3
-rw-r--r--scene/3d/navigation_region_3d.cpp7
-rw-r--r--scene/gui/menu_button.cpp34
-rw-r--r--scene/gui/menu_button.h4
-rw-r--r--scene/gui/option_button.cpp61
-rw-r--r--scene/gui/option_button.h1
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--servers/audio/effects/audio_effect_record.cpp8
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h1
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h2
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl15
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl15
-rw-r--r--servers/rendering/shader_compiler.cpp8
-rw-r--r--servers/rendering/shader_compiler.h2
-rw-r--r--tests/core/math/test_vector3.h8
-rw-r--r--tests/core/math/test_vector3i.h8
-rw-r--r--tests/core/math/test_vector4.h8
-rw-r--r--tests/core/math/test_vector4i.h8
-rw-r--r--tests/core/string/test_string.h16
-rw-r--r--tests/core/templates/test_command_queue.h4
102 files changed, 2413 insertions, 2820 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index bd4eb906c0..7ac048b24a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -15,6 +15,7 @@ SCsub @godotengine/buildsystem
/core/ @godotengine/core
/core/crypto/ @godotengine/network
/core/debugger/ @godotengine/debugger
+/core/extension/ @godotengine/gdextension
/core/input/ @godotengine/input
# Doc
@@ -110,10 +111,9 @@ doc_classes/* @godotengine/documentation
/modules/xatlas_unwrap/ @godotengine/rendering
## Scripting
-/modules/gdnative/ @godotengine/gdnative
/modules/gdscript/ @godotengine/gdscript
/modules/jsonrpc/ @godotengine/gdscript
-/modules/mono/ @godotengine/mono
+/modules/mono/ @godotengine/dotnet
## Text
/modules/freetype/ @godotengine/buildsystem
diff --git a/README.md b/README.md
index 82dbdcfed1..6dfd4fc713 100644
--- a/README.md
+++ b/README.md
@@ -73,5 +73,4 @@ for more information.
[![Code Triagers Badge](https://www.codetriage.com/godotengine/godot/badges/users.svg)](https://www.codetriage.com/godotengine/godot)
[![Translate on Weblate](https://hosted.weblate.org/widgets/godot-engine/-/godot/svg-badge.svg)](https://hosted.weblate.org/engage/godot-engine/?utm_source=widget)
-[![Total alerts on LGTM](https://img.shields.io/lgtm/alerts/g/godotengine/godot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/godotengine/godot/alerts)
[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/godotengine/godot)](https://www.tickgit.com/browse?repo=github.com/godotengine/godot)
diff --git a/SConstruct b/SConstruct
index c86422c4e4..646f41839d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -518,7 +518,7 @@ if selected_platform in platform_list:
# are actually handled to change compile options, etc.
detect.configure(env)
- print(f'Building for platform "{selected_platform}", architecture "{env["arch"]}", target "{env["target"]}.')
+ print(f'Building for platform "{selected_platform}", architecture "{env["arch"]}", target "{env["target"]}".')
if env.dev_build:
print("NOTE: Developer build, with debug optimization level and debug symbols (unless overridden).")
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index a164221fc2..723217875d 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -253,6 +253,10 @@ Error OS::shell_open(String p_uri) {
return ::OS::get_singleton()->shell_open(p_uri);
}
+String OS::read_string_from_stdin(bool p_block) {
+ return ::OS::get_singleton()->get_stdin_string(true);
+}
+
int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr, bool p_open_console) {
List<String> args;
for (int i = 0; i < p_arguments.size(); i++) {
@@ -425,10 +429,6 @@ void OS::delay_msec(int p_msec) const {
::OS::get_singleton()->delay_usec(int64_t(p_msec) * 1000);
}
-bool OS::can_use_threads() const {
- return ::OS::get_singleton()->can_use_threads();
-}
-
bool OS::is_userfs_persistent() const {
return ::OS::get_singleton()->is_userfs_persistent();
}
@@ -530,6 +530,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_system_fonts"), &OS::get_system_fonts);
ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "bold", "italic"), &OS::get_system_font_path, DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
+ ClassDB::bind_method(D_METHOD("read_string_from_stdin", "block"), &OS::read_string_from_stdin, DEFVAL(true));
ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
@@ -561,8 +562,6 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_userfs_persistent"), &OS::is_userfs_persistent);
ClassDB::bind_method(D_METHOD("is_stdout_verbose"), &OS::is_stdout_verbose);
- ClassDB::bind_method(D_METHOD("can_use_threads"), &OS::can_use_threads);
-
ClassDB::bind_method(D_METHOD("is_debug_build"), &OS::is_debug_build);
ClassDB::bind_method(D_METHOD("get_static_memory_usage"), &OS::get_static_memory_usage);
diff --git a/core/core_bind.h b/core/core_bind.h
index ba22971d78..a63ed91137 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -172,6 +172,7 @@ public:
Vector<String> get_system_fonts() const;
String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const;
String get_executable_path() const;
+ String read_string_from_stdin(bool p_block = true);
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
@@ -218,8 +219,6 @@ public:
uint64_t get_ticks_msec() const;
uint64_t get_ticks_usec() const;
- bool can_use_threads() const;
-
bool is_userfs_persistent() const;
bool is_stdout_verbose() const;
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index e9362b4ea4..525362ec7c 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -79,10 +79,8 @@ RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) {
tcp_client = p_tcp;
if (tcp_client.is_valid()) { // Attaching to an already connected stream.
connected = true;
-#ifndef NO_THREADS
running = true;
thread.start(_thread_func, this);
-#endif
} else {
tcp_client.instantiate();
}
@@ -183,10 +181,8 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
return FAILED;
}
connected = true;
-#ifndef NO_THREADS
running = true;
thread.start(_thread_func, this);
-#endif
return OK;
}
@@ -208,9 +204,7 @@ void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) {
}
void RemoteDebuggerPeerTCP::poll() {
-#ifdef NO_THREADS
- _poll();
-#endif
+ // Nothing to do, polling is done in thread.
}
void RemoteDebuggerPeerTCP::_poll() {
diff --git a/core/debugger/remote_debugger_peer.h b/core/debugger/remote_debugger_peer.h
index 473fd8d712..2ab641d537 100644
--- a/core/debugger/remote_debugger_peer.h
+++ b/core/debugger/remote_debugger_peer.h
@@ -43,12 +43,12 @@ protected:
public:
virtual bool is_peer_connected() = 0;
+ virtual int get_max_message_size() const = 0;
virtual bool has_message() = 0;
virtual Error put_message(const Array &p_arr) = 0;
virtual Array get_message() = 0;
virtual void close() = 0;
virtual void poll() = 0;
- virtual int get_max_message_size() const = 0;
virtual bool can_block() const { return true; } // If blocking io is allowed on main thread (debug).
RemoteDebuggerPeer();
@@ -81,12 +81,12 @@ public:
Error connect_to_host(const String &p_host, uint16_t p_port);
- void poll() override;
bool is_peer_connected() override;
+ int get_max_message_size() const override;
bool has_message() override;
- Array get_message() override;
Error put_message(const Array &p_arr) override;
- int get_max_message_size() const override;
+ Array get_message() override;
+ void poll() override;
void close() override;
RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_stream = Ref<StreamPeerTCP>());
diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt
index b2a6160c6c..e11099f380 100644
--- a/core/input/godotcontrollerdb.txt
+++ b/core/input/godotcontrollerdb.txt
@@ -32,6 +32,7 @@ Linux046dc216,046d-c216-Logitech Logitech Dual Action,a:b1,b:b2,y:b3,x:b0,start:
Linux20d6a713,Bensussen Deutsch & Associates Inc.(BDA) NSW Wired controller,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:-a5,dpleft:-a4,dpdown:+a5,dpright:+a4,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Web
Linux054c05c4,Sony Computer Entertainment Wireless Controller,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web
Linux18d19400,18d1-9400-Google LLC Stadia Controller rev. A,a:b0,b:b1,y:b3,x:b2,start:b7,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,platform:Web
+Linux054c0268,054c-0268-Sony PLAYSTATION(R)3 Controller,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web
# UWP
__UWP_GAMEPAD__,Xbox Controller,a:b2,b:b3,x:b4,y:b5,start:b0,back:b1,leftstick:b12,rightstick:b13,leftshoulder:b10,rightshoulder:b11,dpup:b6,dpdown:b7,dpleft:b8,dpright:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:UWP,
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index ad67cfa852..c30acf32bb 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -30,6 +30,8 @@
#include "a_star_grid_2d.h"
+#include "core/variant/typed_array.h"
+
static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) {
real_t dx = (real_t)ABS(p_to.x - p_from.x);
real_t dy = (real_t)ABS(p_to.y - p_from.y);
@@ -492,17 +494,17 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec
return path;
}
-Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
- ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
+TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
+ ERR_FAIL_COND_V_MSG(dirty, TypedArray<Vector2i>(), "Grid is not initialized. Call the update method.");
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
Point *a = _get_point(p_from_id.x, p_from_id.y);
Point *b = _get_point(p_to_id.x, p_to_id.y);
if (a == b) {
- Vector<Vector2> ret;
- ret.push_back(Vector2((float)a->id.x, (float)a->id.y));
+ TypedArray<Vector2i> ret;
+ ret.push_back(a);
return ret;
}
@@ -511,7 +513,7 @@ Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector
bool found_route = _solve(begin_point, end_point);
if (!found_route) {
- return Vector<Vector2>();
+ return TypedArray<Vector2i>();
}
Point *p = end_point;
@@ -521,20 +523,18 @@ Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector
p = p->prev_point;
}
- Vector<Vector2> path;
+ TypedArray<Vector2i> path;
path.resize(pc);
{
- Vector2 *w = path.ptrw();
-
p = end_point;
int64_t idx = pc - 1;
while (p != begin_point) {
- w[idx--] = Vector2((float)p->id.x, (float)p->id.y);
+ path[idx--] = p->id;
p = p->prev_point;
}
- w[0] = p->id;
+ path[0] = p->id;
}
return path;
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index bf6363aa01..1002f18738 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -169,7 +169,7 @@ public:
void clear();
Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to);
- Vector<Vector2> get_id_path(const Vector2i &p_from, const Vector2i &p_to);
+ TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to);
};
VARIANT_ENUM_CAST(AStarGrid2D::DiagonalMode);
diff --git a/core/math/bvh_abb.h b/core/math/bvh_abb.h
index 8a44f1c4da..699f7de604 100644
--- a/core/math/bvh_abb.h
+++ b/core/math/bvh_abb.h
@@ -251,7 +251,9 @@ struct BVH_ABB {
void expand(real_t p_change) {
POINT change;
- change.set_all(p_change);
+ for (int axis = 0; axis < POINT::AXIS_COUNT; ++axis) {
+ change[axis] = p_change;
+ }
grow(change);
}
@@ -262,7 +264,9 @@ struct BVH_ABB {
}
void set_to_max_opposite_extents() {
- neg_max.set_all(FLT_MAX);
+ for (int axis = 0; axis < POINT::AXIS_COUNT; ++axis) {
+ neg_max[axis] = FLT_MAX;
+ }
min = neg_max;
}
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 9441f84087..75364f72f0 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -69,10 +69,6 @@ struct _NO_DISCARD_ Vector2 {
return coord[p_idx];
}
- _FORCE_INLINE_ void set_all(const real_t p_value) {
- x = y = p_value;
- }
-
_FORCE_INLINE_ Vector2::Axis min_axis_index() const {
return x < y ? Vector2::AXIS_X : Vector2::AXIS_Y;
}
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index 4db45fe798..55ba509144 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -45,16 +45,6 @@ Vector3 Vector3::rotated(const Vector3 &p_axis, const real_t p_angle) const {
return r;
}
-void Vector3::set_axis(const int p_axis, const real_t p_value) {
- ERR_FAIL_INDEX(p_axis, 3);
- coord[p_axis] = p_value;
-}
-
-real_t Vector3::get_axis(const int p_axis) const {
- ERR_FAIL_INDEX_V(p_axis, 3, 0);
- return operator[](p_axis);
-}
-
Vector3 Vector3::clamp(const Vector3 &p_min, const Vector3 &p_max) const {
return Vector3(
CLAMP(x, p_min.x, p_max.x),
diff --git a/core/math/vector3.h b/core/math/vector3.h
index 3944afa92e..62e810fb4d 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -68,13 +68,6 @@ struct _NO_DISCARD_ Vector3 {
return coord[p_axis];
}
- void set_axis(const int p_axis, const real_t p_value);
- real_t get_axis(const int p_axis) const;
-
- _FORCE_INLINE_ void set_all(const real_t p_value) {
- x = y = z = p_value;
- }
-
_FORCE_INLINE_ Vector3::Axis min_axis_index() const {
return x < y ? (x < z ? Vector3::AXIS_X : Vector3::AXIS_Z) : (y < z ? Vector3::AXIS_Y : Vector3::AXIS_Z);
}
diff --git a/core/math/vector3i.cpp b/core/math/vector3i.cpp
index b8e74ea6d2..b248f35035 100644
--- a/core/math/vector3i.cpp
+++ b/core/math/vector3i.cpp
@@ -33,16 +33,6 @@
#include "core/math/vector3.h"
#include "core/string/ustring.h"
-void Vector3i::set_axis(const int p_axis, const int32_t p_value) {
- ERR_FAIL_INDEX(p_axis, 3);
- coord[p_axis] = p_value;
-}
-
-int32_t Vector3i::get_axis(const int p_axis) const {
- ERR_FAIL_INDEX_V(p_axis, 3, 0);
- return operator[](p_axis);
-}
-
Vector3i::Axis Vector3i::min_axis_index() const {
return x < y ? (x < z ? Vector3i::AXIS_X : Vector3i::AXIS_Z) : (y < z ? Vector3i::AXIS_Y : Vector3i::AXIS_Z);
}
diff --git a/core/math/vector3i.h b/core/math/vector3i.h
index c6d03cd031..710fd96376 100644
--- a/core/math/vector3i.h
+++ b/core/math/vector3i.h
@@ -66,9 +66,6 @@ struct _NO_DISCARD_ Vector3i {
return coord[p_axis];
}
- void set_axis(const int p_axis, const int32_t p_value);
- int32_t get_axis(const int p_axis) const;
-
Vector3i::Axis min_axis_index() const;
Vector3i::Axis max_axis_index() const;
diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp
index 3c25f454a3..55e51834df 100644
--- a/core/math/vector4.cpp
+++ b/core/math/vector4.cpp
@@ -33,16 +33,6 @@
#include "core/math/basis.h"
#include "core/string/print_string.h"
-void Vector4::set_axis(const int p_axis, const real_t p_value) {
- ERR_FAIL_INDEX(p_axis, 4);
- components[p_axis] = p_value;
-}
-
-real_t Vector4::get_axis(const int p_axis) const {
- ERR_FAIL_INDEX_V(p_axis, 4, 0);
- return operator[](p_axis);
-}
-
Vector4::Axis Vector4::min_axis_index() const {
uint32_t min_index = 0;
real_t min_value = x;
diff --git a/core/math/vector4.h b/core/math/vector4.h
index d89f3ddb05..426c473e13 100644
--- a/core/math/vector4.h
+++ b/core/math/vector4.h
@@ -65,11 +65,6 @@ struct _NO_DISCARD_ Vector4 {
return components[p_axis];
}
- _FORCE_INLINE_ void set_all(const real_t p_value);
-
- void set_axis(const int p_axis, const real_t p_value);
- real_t get_axis(const int p_axis) const;
-
Vector4::Axis min_axis_index() const;
Vector4::Axis max_axis_index() const;
@@ -150,10 +145,6 @@ struct _NO_DISCARD_ Vector4 {
}
};
-void Vector4::set_all(const real_t p_value) {
- x = y = z = p_value;
-}
-
real_t Vector4::dot(const Vector4 &p_vec4) const {
return x * p_vec4.x + y * p_vec4.y + z * p_vec4.z + w * p_vec4.w;
}
diff --git a/core/math/vector4i.cpp b/core/math/vector4i.cpp
index a89b802675..77f6fbd5b7 100644
--- a/core/math/vector4i.cpp
+++ b/core/math/vector4i.cpp
@@ -33,16 +33,6 @@
#include "core/math/vector4.h"
#include "core/string/ustring.h"
-void Vector4i::set_axis(const int p_axis, const int32_t p_value) {
- ERR_FAIL_INDEX(p_axis, 4);
- coord[p_axis] = p_value;
-}
-
-int32_t Vector4i::get_axis(const int p_axis) const {
- ERR_FAIL_INDEX_V(p_axis, 4, 0);
- return operator[](p_axis);
-}
-
Vector4i::Axis Vector4i::min_axis_index() const {
uint32_t min_index = 0;
int32_t min_value = x;
diff --git a/core/math/vector4i.h b/core/math/vector4i.h
index fdf33b9569..a32414bb18 100644
--- a/core/math/vector4i.h
+++ b/core/math/vector4i.h
@@ -68,9 +68,6 @@ struct _NO_DISCARD_ Vector4i {
return coord[p_axis];
}
- void set_axis(const int p_axis, const int32_t p_value);
- int32_t get_axis(const int p_axis) const;
-
Vector4i::Axis min_axis_index() const;
Vector4i::Axis max_axis_index() const;
diff --git a/core/os/mutex.cpp b/core/os/mutex.cpp
index 1d4400bfc1..512db1737a 100644
--- a/core/os/mutex.cpp
+++ b/core/os/mutex.cpp
@@ -40,11 +40,7 @@ void _global_unlock() {
_global_mutex.unlock();
}
-#ifndef NO_THREADS
-
template class MutexImpl<std::recursive_mutex>;
template class MutexImpl<std::mutex>;
template class MutexLock<MutexImpl<std::recursive_mutex>>;
template class MutexLock<MutexImpl<std::mutex>>;
-
-#endif
diff --git a/core/os/mutex.h b/core/os/mutex.h
index a51248807b..eb58418bd6 100644
--- a/core/os/mutex.h
+++ b/core/os/mutex.h
@@ -34,8 +34,6 @@
#include "core/error/error_list.h"
#include "core/typedefs.h"
-#if !defined(NO_THREADS)
-
#include <mutex>
template <class StdMutexT>
@@ -79,29 +77,4 @@ extern template class MutexImpl<std::mutex>;
extern template class MutexLock<MutexImpl<std::recursive_mutex>>;
extern template class MutexLock<MutexImpl<std::mutex>>;
-#else
-
-class FakeMutex {
- FakeMutex() {}
-};
-
-template <class MutexT>
-class MutexImpl {
-public:
- _ALWAYS_INLINE_ void lock() const {}
- _ALWAYS_INLINE_ void unlock() const {}
- _ALWAYS_INLINE_ Error try_lock() const { return OK; }
-};
-
-template <class MutexT>
-class MutexLock {
-public:
- explicit MutexLock(const MutexT &p_mutex) {}
-};
-
-using Mutex = MutexImpl<FakeMutex>;
-using BinaryMutex = MutexImpl<FakeMutex>; // Non-recursive, handle with care
-
-#endif // !NO_THREADS
-
#endif // MUTEX_H
diff --git a/core/os/os.cpp b/core/os/os.cpp
index ee8da21751..2bdede889f 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -328,14 +328,6 @@ String OS::get_processor_name() const {
return "";
}
-bool OS::can_use_threads() const {
-#ifdef NO_THREADS
- return false;
-#else
- return true;
-#endif
-}
-
void OS::set_has_server_feature_callback(HasServerFeatureCallback p_callback) {
has_server_feature_callback = p_callback;
}
diff --git a/core/os/os.h b/core/os/os.h
index aa45a3b8a8..1a5e45968d 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -299,8 +299,6 @@ public:
virtual String get_unique_id() const;
- virtual bool can_use_threads() const;
-
bool has_feature(const String &p_feature);
void set_has_server_feature_callback(HasServerFeatureCallback p_callback);
diff --git a/core/os/rw_lock.h b/core/os/rw_lock.h
index a046f474ea..d3206547c7 100644
--- a/core/os/rw_lock.h
+++ b/core/os/rw_lock.h
@@ -33,8 +33,6 @@
#include "core/error/error_list.h"
-#if !defined(NO_THREADS)
-
#include <shared_mutex>
class RWLock {
@@ -72,21 +70,6 @@ public:
}
};
-#else
-
-class RWLock {
-public:
- void read_lock() const {}
- void read_unlock() const {}
- Error read_try_lock() const { return OK; }
-
- void write_lock() {}
- void write_unlock() {}
- Error write_try_lock() { return OK; }
-};
-
-#endif
-
class RWLockRead {
const RWLock &lock;
diff --git a/core/os/semaphore.h b/core/os/semaphore.h
index 72df52dd34..1a93d3ee2c 100644
--- a/core/os/semaphore.h
+++ b/core/os/semaphore.h
@@ -34,8 +34,6 @@
#include "core/error/error_list.h"
#include "core/typedefs.h"
-#if !defined(NO_THREADS)
-
#include <condition_variable>
#include <mutex>
@@ -70,15 +68,4 @@ public:
}
};
-#else
-
-class Semaphore {
-public:
- _ALWAYS_INLINE_ void post() const {}
- _ALWAYS_INLINE_ void wait() const {}
- _ALWAYS_INLINE_ bool try_wait() const { return true; }
-};
-
-#endif
-
#endif // SEMAPHORE_H
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index c8072b7280..e8ebb4597f 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -34,9 +34,6 @@
#include "thread.h"
#include "core/object/script_language.h"
-
-#if !defined(NO_THREADS)
-
#include "core/templates/safe_refcount.h"
Error (*Thread::set_name_func)(const String &) = nullptr;
@@ -128,5 +125,4 @@ Thread::~Thread() {
}
}
-#endif
#endif // PLATFORM_THREAD_OVERRIDE
diff --git a/core/os/thread.h b/core/os/thread.h
index 0fb283e224..7462ec17fb 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -35,15 +35,14 @@
#ifdef PLATFORM_THREAD_OVERRIDE
#include "platform_thread.h"
#else
+
#ifndef THREAD_H
#define THREAD_H
+#include "core/templates/safe_refcount.h"
#include "core/typedefs.h"
-#if !defined(NO_THREADS)
-#include "core/templates/safe_refcount.h"
#include <thread>
-#endif
class String;
@@ -65,7 +64,6 @@ public:
};
private:
-#if !defined(NO_THREADS)
friend class Main;
static ID main_thread_id;
@@ -82,7 +80,6 @@ private:
static void (*set_priority_func)(Thread::Priority);
static void (*init_func)();
static void (*term_func)();
-#endif
public:
static void _set_platform_funcs(
@@ -91,7 +88,6 @@ public:
void (*p_init_func)() = nullptr,
void (*p_term_func)() = nullptr);
-#if !defined(NO_THREADS)
_FORCE_INLINE_ ID get_id() const { return id; }
// get the ID of the caller thread
_FORCE_INLINE_ static ID get_caller_id() { return caller_id; }
@@ -107,19 +103,6 @@ public:
Thread();
~Thread();
-#else
- _FORCE_INLINE_ ID get_id() const { return 0; }
- // get the ID of the caller thread
- _FORCE_INLINE_ static ID get_caller_id() { return 0; }
- // get the ID of the main thread
- _FORCE_INLINE_ static ID get_main_id() { return 0; }
-
- static Error set_name(const String &p_name) { return ERR_UNAVAILABLE; }
-
- void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()) {}
- bool is_started() const { return false; }
- void wait_to_finish() {}
-#endif
};
#endif // THREAD_H
diff --git a/core/os/threaded_array_processor.h b/core/os/threaded_array_processor.h
index 935fc7a360..95a2253f14 100644
--- a/core/os/threaded_array_processor.h
+++ b/core/os/threaded_array_processor.h
@@ -49,8 +49,6 @@ struct ThreadArrayProcessData {
}
};
-#ifndef NO_THREADS
-
template <class T>
void process_array_thread(void *ud) {
T &data = *(T *)ud;
@@ -86,21 +84,4 @@ void thread_process_array(uint32_t p_elements, C *p_instance, M p_method, U p_us
memdelete_arr(threads);
}
-#else
-
-template <class C, class M, class U>
-void thread_process_array(uint32_t p_elements, C *p_instance, M p_method, U p_userdata) {
- ThreadArrayProcessData<C, U> data;
- data.method = p_method;
- data.instance = p_instance;
- data.userdata = p_userdata;
- data.index.set(0);
- data.elements = p_elements;
- for (uint32_t i = 0; i < p_elements; i++) {
- data.process(i);
- }
-}
-
-#endif
-
#endif // THREADED_ARRAY_PROCESSOR_H
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 75b797d397..6218c21cde 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -4651,15 +4651,18 @@ String String::sprintf(const Array &values, bool *error) const {
double value = values[value_index];
bool is_negative = (value < 0);
String str = String::num(ABS(value), min_decimals);
+ bool not_numeric = isinf(value) || isnan(value);
// Pad decimals out.
- str = str.pad_decimals(min_decimals);
+ if (!not_numeric) {
+ str = str.pad_decimals(min_decimals);
+ }
int initial_len = str.length();
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeros ? String("0") : String(" ");
+ String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4709,14 +4712,19 @@ String String::sprintf(const Array &values, bool *error) const {
String str = "(";
for (int i = 0; i < count; i++) {
double val = vec[i];
+ String number_str = String::num(ABS(val), min_decimals);
+ bool not_numeric = isinf(val) || isnan(val);
+
// Pad decimals out.
- String number_str = String::num(ABS(val), min_decimals).pad_decimals(min_decimals);
+ if (!not_numeric) {
+ number_str = number_str.pad_decimals(min_decimals);
+ }
int initial_len = number_str.length();
// Padding. Leave room for sign later if required.
int pad_chars_count = val < 0 ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeros ? String("0") : String(" ");
+ String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros
if (left_justified) {
number_str = number_str.rpad(pad_chars_count, pad_char);
} else {
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index f98b2308c9..1482e3eef1 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -46,9 +46,7 @@ class CharString;
template <class T, class V>
class VMap;
-#if !defined(NO_THREADS)
SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t)
-#endif
// Silence a false positive warning (see GH-52119).
#if defined(__GNUC__) && !defined(__clang__)
diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h
index e850f3bd5e..95e8ca1a14 100644
--- a/core/templates/safe_list.h
+++ b/core/templates/safe_list.h
@@ -33,11 +33,9 @@
#include "core/os/memory.h"
#include "core/typedefs.h"
-#include <functional>
-
-#if !defined(NO_THREADS)
#include <atomic>
+#include <functional>
#include <type_traits>
// Design goals for these classes:
@@ -239,159 +237,4 @@ public:
}
};
-#else // NO_THREADS
-
-// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly.
-template <class T, class A = DefaultAllocator>
-class SafeList {
- struct SafeListNode {
- SafeListNode *next = nullptr;
-
- // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted.
- SafeListNode *graveyard_next = nullptr;
-
- std::function<void(T)> deletion_fn = [](T t) { return; };
-
- T val;
- };
-
- SafeListNode *head = nullptr;
- SafeListNode *graveyard_head = nullptr;
-
- unsigned int active_iterator_count = 0;
-
-public:
- class Iterator {
- friend class SafeList;
-
- SafeListNode *cursor = nullptr;
- SafeList *list = nullptr;
-
- public:
- Iterator(SafeListNode *p_cursor, SafeList *p_list) :
- cursor(p_cursor), list(p_list) {
- list->active_iterator_count++;
- }
-
- ~Iterator() {
- list->active_iterator_count--;
- }
-
- T &operator*() {
- return cursor->val;
- }
-
- Iterator &operator++() {
- cursor = cursor->next;
- return *this;
- }
-
- // These two operators are mostly useful for comparisons to nullptr.
- bool operator==(const void *p_other) const {
- return cursor == p_other;
- }
-
- bool operator!=(const void *p_other) const {
- return cursor != p_other;
- }
-
- // These two allow easy range-based for loops.
- bool operator==(const Iterator &p_other) const {
- return cursor == p_other.cursor;
- }
-
- bool operator!=(const Iterator &p_other) const {
- return cursor != p_other.cursor;
- }
- };
-
-public:
- // Calling this will cause an allocation.
- void insert(T p_value) {
- SafeListNode *new_node = memnew_allocator(SafeListNode, A);
- new_node->val = p_value;
- new_node->next = head;
- head = new_node;
- }
-
- Iterator find(T p_value) {
- for (Iterator it = begin(); it != end(); ++it) {
- if (*it == p_value) {
- return it;
- }
- }
- return end();
- }
-
- void erase(T p_value, std::function<void(T)> p_deletion_fn) {
- erase(find(p_value), p_deletion_fn);
- }
-
- void erase(T p_value) {
- erase(find(p_value), [](T t) { return; });
- }
-
- void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) {
- p_iterator.cursor->deletion_fn = p_deletion_fn;
- erase(p_iterator);
- }
-
- void erase(Iterator p_iterator) {
- Iterator prev = begin();
- for (; prev != end(); ++prev) {
- if (prev.cursor && prev.cursor->next == p_iterator.cursor) {
- break;
- }
- }
- if (prev == end()) {
- // Not in the list, nothing to do.
- return;
- }
- // First, remove the node from the list.
- prev.cursor->next = p_iterator.cursor->next;
-
- // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node.
- p_iterator.cursor->graveyard_next = graveyard_head;
- graveyard_head = p_iterator.cursor;
- }
-
- Iterator begin() {
- return Iterator(head, this);
- }
-
- Iterator end() {
- return Iterator(nullptr, this);
- }
-
- // Calling this will cause zero to many deallocations.
- bool maybe_cleanup() {
- SafeListNode *cursor = graveyard_head;
- if (active_iterator_count != 0) {
- // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete.
- return false;
- }
- graveyard_head = nullptr;
- // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion.
- while (cursor) {
- SafeListNode *tmp = cursor;
- cursor = cursor->next;
- tmp->deletion_fn(tmp->val);
- memdelete_allocator<SafeListNode, A>(tmp);
- }
- return true;
- }
-
- ~SafeList() {
-#ifdef DEBUG_ENABLED
- if (!maybe_cleanup()) {
- ERR_PRINT("There are still iterators around when destructing a SafeList. Memory will be leaked. This is a bug.");
- }
-#else
- maybe_cleanup();
-#endif
- }
-};
-
-#endif
-
#endif // SAFE_LIST_H
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h
index 1f6551762e..c4ffe5ca02 100644
--- a/core/templates/safe_refcount.h
+++ b/core/templates/safe_refcount.h
@@ -33,8 +33,6 @@
#include "core/typedefs.h"
-#if !defined(NO_THREADS)
-
#include <atomic>
#include <type_traits>
@@ -191,141 +189,4 @@ public:
}
};
-#else
-
-template <class T>
-class SafeNumeric {
-protected:
- T value;
-
-public:
- _ALWAYS_INLINE_ void set(T p_value) {
- value = p_value;
- }
-
- _ALWAYS_INLINE_ T get() const {
- return value;
- }
-
- _ALWAYS_INLINE_ T increment() {
- return ++value;
- }
-
- _ALWAYS_INLINE_ T postincrement() {
- return value++;
- }
-
- _ALWAYS_INLINE_ T decrement() {
- return --value;
- }
-
- _ALWAYS_INLINE_ T postdecrement() {
- return value--;
- }
-
- _ALWAYS_INLINE_ T add(T p_value) {
- return value += p_value;
- }
-
- _ALWAYS_INLINE_ T postadd(T p_value) {
- T old = value;
- value += p_value;
- return old;
- }
-
- _ALWAYS_INLINE_ T sub(T p_value) {
- return value -= p_value;
- }
-
- _ALWAYS_INLINE_ T postsub(T p_value) {
- T old = value;
- value -= p_value;
- return old;
- }
-
- _ALWAYS_INLINE_ T exchange_if_greater(T p_value) {
- if (value < p_value) {
- value = p_value;
- }
- return value;
- }
-
- _ALWAYS_INLINE_ T conditional_increment() {
- if (value == 0) {
- return 0;
- } else {
- return ++value;
- }
- }
-
- _ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) :
- value(p_value) {
- }
-};
-
-class SafeFlag {
-protected:
- bool flag;
-
-public:
- _ALWAYS_INLINE_ bool is_set() const {
- return flag;
- }
-
- _ALWAYS_INLINE_ void set() {
- flag = true;
- }
-
- _ALWAYS_INLINE_ void clear() {
- flag = false;
- }
-
- _ALWAYS_INLINE_ void set_to(bool p_value) {
- flag = p_value;
- }
-
- _ALWAYS_INLINE_ explicit SafeFlag(bool p_value = false) :
- flag(p_value) {}
-};
-
-class SafeRefCount {
- uint32_t count = 0;
-
-public:
- _ALWAYS_INLINE_ bool ref() { // true on success
- if (count != 0) {
- ++count;
- return true;
- } else {
- return false;
- }
- }
-
- _ALWAYS_INLINE_ uint32_t refval() { // none-zero on success
- if (count != 0) {
- return ++count;
- } else {
- return 0;
- }
- }
-
- _ALWAYS_INLINE_ bool unref() { // true if must be disposed of
- return --count == 0;
- }
-
- _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of
- return --count;
- }
-
- _ALWAYS_INLINE_ uint32_t get() const {
- return count;
- }
-
- _ALWAYS_INLINE_ void init(uint32_t p_value = 1) {
- count = p_value;
- }
-};
-
-#endif
-
#endif // SAFE_REFCOUNT_H
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index 331862ebfa..8dde3748d7 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -53,7 +53,7 @@
</description>
</method>
<method name="get_id_path">
- <return type="PackedVector2Array" />
+ <return type="Vector2i[]" />
<param index="0" name="from_id" type="Vector2i" />
<param index="1" name="to_id" type="Vector2i" />
<description>
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index d62f704528..6d9b679fbc 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -83,7 +83,7 @@
<return type="Vector3" />
<param index="0" name="order" type="int" default="2" />
<description>
- Returns the basis's rotation in the form of Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
+ Returns the basis's rotation in the form of Euler angles. The Euler order depends on the [param order] parameter, by default it uses the YXZ convention: when decomposing, first Z, then X, and Y last. The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
Consider using the [method get_rotation_quaternion] method instead, which returns a [Quaternion] quaternion instead of Euler angles.
</description>
</method>
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index f4ae70cf22..e52f897164 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -20,13 +20,13 @@
[csharp]
public void Save(string content)
{
- using var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Write);
+ using var file = FileAccess.Open("user://save_game.dat", FileAccess.ModeFlags.Write);
file.StoreString(content);
}
public string Load()
{
- using var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Read);
+ using var file = FileAccess.Open("user://save_game.dat", FileAccess.ModeFlags.Read);
string content = file.GetAsText();
return content;
}
@@ -316,8 +316,7 @@
return (unsigned + MAX_15B) % MAX_16B - MAX_15B
func _ready():
- var f = File.new()
- f.open("user://file.dat", File.WRITE_READ)
+ var f = FileAccess.open("user://file.dat", FileAccess.WRITE_READ)
f.store_16(-42) # This wraps around and stores 65494 (2^16 - 42).
f.store_16(121) # In bounds, will store 121.
f.seek(0) # Go back to start to read the stored value.
@@ -329,8 +328,7 @@
[csharp]
public override void _Ready()
{
- var f = new File();
- f.Open("user://file.dat", File.ModeFlags.WriteRead);
+ using var f = FileAccess.Open("user://file.dat", FileAccess.ModeFlags.WriteRead);
f.Store16(unchecked((ushort)-42)); // This wraps around and stores 65494 (2^16 - 42).
f.Store16(121); // In bounds, will store 121.
f.Seek(0); // Go back to start to read the stored value.
diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml
index 1f38510e83..4d5d5a011b 100644
--- a/doc/classes/MenuButton.xml
+++ b/doc/classes/MenuButton.xml
@@ -25,6 +25,12 @@
If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button.
</description>
</method>
+ <method name="show_popup">
+ <return type="void" />
+ <description>
+ Adjusts popup position and sizing for the [MenuButton], then shows the [PopupMenu]. Prefer this over using [code]get_popup().popup()[/code].
+ </description>
+ </method>
</methods>
<members>
<member name="action_mode" type="int" setter="set_action_mode" getter="get_action_mode" overrides="BaseButton" enum="BaseButton.ActionMode" default="0" />
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 313f3ab6db..bc6ac8012f 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -18,12 +18,6 @@
Displays a modal dialog box using the host OS' facilities. Execution is blocked until the dialog is closed.
</description>
</method>
- <method name="can_use_threads" qualifiers="const">
- <return type="bool" />
- <description>
- Returns [code]true[/code] if the current host platform is using multiple threads.
- </description>
- </method>
<method name="close_midi_inputs">
<return type="void" />
<description>
@@ -544,6 +538,14 @@
[b]Note:[/b] This method is implemented on Linux, macOS and Windows.
</description>
</method>
+ <method name="read_string_from_stdin">
+ <return type="String" />
+ <param index="0" name="block" type="bool" default="true" />
+ <description>
+ Reads a user input string from the standard input (usually the terminal).
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows. Non-blocking reads are not currently supported on any platform.
+ </description>
+ </method>
<method name="request_permission">
<return type="bool" />
<param index="0" name="name" type="String" />
diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml
index f10c096c1b..199535298c 100644
--- a/doc/classes/OptionButton.xml
+++ b/doc/classes/OptionButton.xml
@@ -190,6 +190,12 @@
Sets the tooltip of the item at index [param idx].
</description>
</method>
+ <method name="show_popup">
+ <return type="void" />
+ <description>
+ Adjusts popup position and sizing for the [OptionButton], then shows the [PopupMenu]. Prefer this over using [code]get_popup().popup()[/code].
+ </description>
+ </method>
</methods>
<members>
<member name="action_mode" type="int" setter="set_action_mode" getter="get_action_mode" overrides="BaseButton" enum="BaseButton.ActionMode" default="0" />
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index c8a42e925e..1151c2232e 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -126,9 +126,7 @@ int OS_Unix::unix_initialize_audio(int p_audio_driver) {
}
void OS_Unix::initialize_core() {
-#if !defined(NO_THREADS)
init_thread_posix();
-#endif
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
diff --git a/drivers/unix/thread_posix.cpp b/drivers/unix/thread_posix.cpp
index cb5f261e6e..6292d8b3bc 100644
--- a/drivers/unix/thread_posix.cpp
+++ b/drivers/unix/thread_posix.cpp
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#if (defined(UNIX_ENABLED) || defined(PTHREAD_ENABLED)) && !defined(NO_THREADS)
+#if defined(UNIX_ENABLED) || defined(PTHREAD_ENABLED)
#include "thread_posix.h"
@@ -73,4 +73,4 @@ void init_thread_posix() {
Thread::_set_platform_funcs(&set_name, nullptr);
}
-#endif
+#endif // UNIX_ENABLED || PTHREAD_ENABLED
diff --git a/drivers/unix/thread_posix.h b/drivers/unix/thread_posix.h
index 672adcba72..87e42b3870 100644
--- a/drivers/unix/thread_posix.h
+++ b/drivers/unix/thread_posix.h
@@ -31,8 +31,6 @@
#ifndef THREAD_POSIX_H
#define THREAD_POSIX_H
-#if !defined(NO_THREADS)
void init_thread_posix();
-#endif
#endif // THREAD_POSIX_H
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 270f4560b7..65c65a517f 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -716,11 +716,11 @@ void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
- if (ED_IS_SHORTCUT("property_editor/copy_property", p_event)) {
- menu_option(MENU_COPY_PROPERTY);
+ if (ED_IS_SHORTCUT("property_editor/copy_value", p_event)) {
+ menu_option(MENU_COPY_VALUE);
accept_event();
- } else if (ED_IS_SHORTCUT("property_editor/paste_property", p_event) && !is_read_only()) {
- menu_option(MENU_PASTE_PROPERTY);
+ } else if (ED_IS_SHORTCUT("property_editor/paste_value", p_event) && !is_read_only()) {
+ menu_option(MENU_PASTE_VALUE);
accept_event();
} else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) {
menu_option(MENU_COPY_PROPERTY_PATH);
@@ -915,10 +915,10 @@ Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
void EditorProperty::menu_option(int p_option) {
switch (p_option) {
- case MENU_COPY_PROPERTY: {
+ case MENU_COPY_VALUE: {
InspectorDock::get_inspector_singleton()->set_property_clipboard(object->get(property));
} break;
- case MENU_PASTE_PROPERTY: {
+ case MENU_PASTE_VALUE: {
emit_changed(property, InspectorDock::get_inspector_singleton()->get_property_clipboard());
} break;
case MENU_COPY_PROPERTY_PATH: {
@@ -1013,10 +1013,10 @@ void EditorProperty::_update_popup() {
add_child(menu);
menu->connect("id_pressed", callable_mp(this, &EditorProperty::menu_option));
}
- menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_property"), MENU_COPY_PROPERTY);
- menu->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/paste_property"), MENU_PASTE_PROPERTY);
+ menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_value"), MENU_COPY_VALUE);
+ menu->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/paste_value"), MENU_PASTE_VALUE);
menu->add_icon_shortcut(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH);
- menu->set_item_disabled(MENU_PASTE_PROPERTY, is_read_only());
+ menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
if (!pin_hidden) {
menu->add_separator();
if (can_pin) {
@@ -4101,7 +4101,7 @@ EditorInspector::EditorInspector() {
refresh_countdown = 0.33;
}
- ED_SHORTCUT("property_editor/copy_property", TTR("Copy Property"), KeyModifierMask::CMD_OR_CTRL | Key::C);
- ED_SHORTCUT("property_editor/paste_property", TTR("Paste Property"), KeyModifierMask::CMD_OR_CTRL | Key::V);
+ ED_SHORTCUT("property_editor/copy_value", TTR("Copy Value"), KeyModifierMask::CMD_OR_CTRL | Key::C);
+ ED_SHORTCUT("property_editor/paste_value", TTR("Paste Value"), KeyModifierMask::CMD_OR_CTRL | Key::V);
ED_SHORTCUT("property_editor/copy_property_path", TTR("Copy Property Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);
}
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 872007e637..bada02e254 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -58,8 +58,8 @@ class EditorProperty : public Container {
public:
enum MenuItems {
- MENU_COPY_PROPERTY,
- MENU_PASTE_PROPERTY,
+ MENU_COPY_VALUE,
+ MENU_PASTE_VALUE,
MENU_COPY_PROPERTY_PATH,
MENU_PIN_VALUE,
MENU_OPEN_DOCUMENTATION,
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index de8259c25c..f89aef4cc3 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -155,7 +155,7 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
any_type_matches = is_global_class ? EditorNode::get_editor_data().script_class_is_parent(res_type, base) : loaded_resource->is_class(base);
- if (!any_type_matches) {
+ if (any_type_matches) {
break;
}
}
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index 4cd046e811..5edb6d877c 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -398,7 +398,7 @@ void EditorSpinSlider::_draw_spin_slider() {
grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.get_center();
- bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
+ bool display_grabber = (grabbing_grabber || mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
if (grabber->is_visible() != display_grabber) {
if (display_grabber) {
grabber->show();
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 18561fe3a0..f70f0dc0aa 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -49,6 +49,7 @@
#include "editor/find_in_files.h"
#include "editor/node_dock.h"
#include "editor/plugins/shader_editor_plugin.h"
+#include "editor/plugins/text_shader_editor.h"
#include "scene/main/window.h"
#include "scene/scene_string_names.h"
#include "script_text_editor.h"
@@ -1124,6 +1125,7 @@ TypedArray<Script> ScriptEditor::_get_open_scripts() const {
bool ScriptEditor::toggle_scripts_panel() {
list_split->set_visible(!list_split->is_visible());
+ EditorSettings::get_singleton()->set_project_metadata("scripts_panel", "show_scripts_panel", list_split->is_visible());
return list_split->is_visible();
}
@@ -1611,7 +1613,7 @@ void ScriptEditor::_notification(int p_what) {
EditorNode::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
} break;
- case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
+ case NOTIFICATION_APPLICATION_FOCUS_IN: {
_test_script_times_on_disk();
_update_modified_scripts_for_external_editor();
} break;
@@ -2134,16 +2136,6 @@ void ScriptEditor::_update_script_names() {
_update_script_colors();
}
-void ScriptEditor::_update_script_connections() {
- for (int i = 0; i < tab_container->get_tab_count(); i++) {
- ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(tab_container->get_tab_control(i));
- if (!ste) {
- continue;
- }
- ste->_update_connected_methods();
- }
-}
-
Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const {
if (r_error) {
*r_error = ERR_FILE_CANT_OPEN;
@@ -2817,7 +2809,6 @@ void ScriptEditor::_tree_changed() {
waiting_update_names = true;
call_deferred(SNAME("_update_script_names"));
- call_deferred(SNAME("_update_script_connections"));
}
void ScriptEditor::_split_dragged(float) {
@@ -3614,7 +3605,6 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_goto_script_line2", &ScriptEditor::_goto_script_line2);
ClassDB::bind_method("_copy_script_path", &ScriptEditor::_copy_script_path);
- ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections);
ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open);
ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);
ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts);
@@ -3697,6 +3687,7 @@ ScriptEditor::ScriptEditor() {
overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
list_split->add_child(overview_vbox);
+ list_split->set_visible(EditorSettings::get_singleton()->get_project_metadata("scripts_panel", "show_scripts_panel", true));
buttons_hbox = memnew(HBoxContainer);
overview_vbox->add_child(buttons_hbox);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 1e78dc4ec4..233baa9bd3 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -403,7 +403,6 @@ class ScriptEditor : public PanelContainer {
void _filter_scripts_text_changed(const String &p_newtext);
void _filter_methods_text_changed(const String &p_newtext);
void _update_script_names();
- void _update_script_connections();
bool _sort_list_on_update;
void _members_overview_selected(int p_idx);
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index c21b4356f8..a1d24907e5 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -289,6 +289,7 @@ void ScriptTextEditor::reload_text() {
te->tag_saved_version();
code_editor->update_line_and_column();
+ _validate_script();
}
void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 2eafa4fc91..de1a807721 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -30,1172 +30,12 @@
#include "shader_editor_plugin.h"
-#include "core/io/resource_loader.h"
-#include "core/io/resource_saver.h"
-#include "core/os/keyboard.h"
-#include "core/os/os.h"
-#include "core/version_generated.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
-#include "editor/editor_settings.h"
#include "editor/filesystem_dock.h"
+#include "editor/plugins/text_shader_editor.h"
#include "editor/plugins/visual_shader_editor_plugin.h"
-#include "editor/project_settings_editor.h"
#include "editor/shader_create_dialog.h"
-#include "scene/gui/split_container.h"
-#include "servers/display_server.h"
-#include "servers/rendering/shader_preprocessor.h"
-#include "servers/rendering/shader_types.h"
-
-/*** SHADER SYNTAX HIGHLIGHTER ****/
-
-Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
- Dictionary color_map;
-
- for (const Point2i &region : disabled_branch_regions) {
- if (p_line >= region.x && p_line <= region.y) {
- Dictionary highlighter_info;
- highlighter_info["color"] = disabled_branch_color;
-
- color_map[0] = highlighter_info;
- return color_map;
- }
- }
-
- return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line);
-}
-
-void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) {
- ERR_FAIL_COND(p_region.x < 0);
- ERR_FAIL_COND(p_region.y < 0);
-
- for (int i = 0; i < disabled_branch_regions.size(); i++) {
- ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists.");
- }
-
- Point2i disabled_branch_region;
- disabled_branch_region.x = p_region.x;
- disabled_branch_region.y = p_region.y;
- disabled_branch_regions.push_back(disabled_branch_region);
-
- clear_highlighting_cache();
-}
-
-void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() {
- disabled_branch_regions.clear();
- clear_highlighting_cache();
-}
-
-void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) {
- disabled_branch_color = p_color;
- clear_highlighting_cache();
-}
-
-/*** SHADER SCRIPT EDITOR ****/
-
-static bool saved_warnings_enabled = false;
-static bool saved_treat_warning_as_errors = false;
-static HashMap<ShaderWarning::Code, bool> saved_warnings;
-static uint32_t saved_warning_flags = 0U;
-
-void ShaderTextEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_THEME_CHANGED: {
- if (is_visible_in_tree()) {
- _load_theme_settings();
- if (warnings.size() > 0 && last_compile_result == OK) {
- warnings_panel->clear();
- _update_warning_panel();
- }
- }
- } break;
- }
-}
-
-Ref<Shader> ShaderTextEditor::get_edited_shader() const {
- return shader;
-}
-
-Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const {
- return shader_inc;
-}
-
-void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) {
- set_edited_shader(p_shader, p_shader->get_code());
-}
-
-void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) {
- if (shader == p_shader) {
- return;
- }
- if (shader.is_valid()) {
- shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
- }
- shader = p_shader;
- shader_inc = Ref<ShaderInclude>();
-
- set_edited_code(p_code);
-
- if (shader.is_valid()) {
- shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
- }
-}
-
-void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
- set_edited_shader_include(p_shader_inc, p_shader_inc->get_code());
-}
-
-void ShaderTextEditor::_shader_changed() {
- // This function is used for dependencies (include changing changes main shader and forces it to revalidate)
- if (block_shader_changed) {
- return;
- }
- dependencies_version++;
- _validate_script();
-}
-
-void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) {
- if (shader_inc == p_shader_inc) {
- return;
- }
- if (shader_inc.is_valid()) {
- shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
- }
- shader_inc = p_shader_inc;
- shader = Ref<Shader>();
-
- set_edited_code(p_code);
-
- if (shader_inc.is_valid()) {
- shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
- }
-}
-
-void ShaderTextEditor::set_edited_code(const String &p_code) {
- _load_theme_settings();
-
- get_text_editor()->set_text(p_code);
- get_text_editor()->clear_undo_history();
- get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0);
- get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0);
- get_text_editor()->tag_saved_version();
-
- _validate_script();
- _line_col_changed();
-}
-
-void ShaderTextEditor::reload_text() {
- ERR_FAIL_COND(shader.is_null());
-
- CodeEdit *te = get_text_editor();
- int column = te->get_caret_column();
- int row = te->get_caret_line();
- int h = te->get_h_scroll();
- int v = te->get_v_scroll();
-
- te->set_text(shader->get_code());
- te->set_caret_line(row);
- te->set_caret_column(column);
- te->set_h_scroll(h);
- te->set_v_scroll(v);
-
- te->tag_saved_version();
-
- update_line_and_column();
-}
-
-void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
- warnings_panel = p_warnings_panel;
-}
-
-void ShaderTextEditor::_load_theme_settings() {
- CodeEdit *text_editor = get_text_editor();
- Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color");
- if (updated_marked_line_color != marked_line_color) {
- for (int i = 0; i < text_editor->get_line_count(); i++) {
- if (text_editor->get_line_background_color(i) == marked_line_color) {
- text_editor->set_line_background_color(i, updated_marked_line_color);
- }
- }
- marked_line_color = updated_marked_line_color;
- }
-
- syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
- syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
- syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
- syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
-
- syntax_highlighter->clear_keyword_colors();
-
- const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
- const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
-
- List<String> keywords;
- ShaderLanguage::get_keyword_list(&keywords);
-
- for (const String &E : keywords) {
- if (ShaderLanguage::is_control_flow_keyword(E)) {
- syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
- } else {
- syntax_highlighter->add_keyword_color(E, keyword_color);
- }
- }
-
- List<String> pp_keywords;
- ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
-
- for (const String &E : pp_keywords) {
- syntax_highlighter->add_keyword_color(E, keyword_color);
- }
-
- // Colorize built-ins like `COLOR` differently to make them easier
- // to distinguish from keywords at a quick glance.
-
- List<String> built_ins;
-
- if (shader_inc.is_valid()) {
- for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
- for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) {
- for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
- built_ins.push_back(F.key);
- }
- }
-
- const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
-
- for (int j = 0; j < modes.size(); j++) {
- const ShaderLanguage::ModeInfo &info = modes[j];
-
- if (!info.options.is_empty()) {
- for (int k = 0; k < info.options.size(); k++) {
- built_ins.push_back(String(info.name) + "_" + String(info.options[k]));
- }
- } else {
- built_ins.push_back(String(info.name));
- }
- }
- }
- } else if (shader.is_valid()) {
- for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) {
- for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
- built_ins.push_back(F.key);
- }
- }
-
- const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
-
- for (int i = 0; i < modes.size(); i++) {
- const ShaderLanguage::ModeInfo &info = modes[i];
-
- if (!info.options.is_empty()) {
- for (int j = 0; j < info.options.size(); j++) {
- built_ins.push_back(String(info.name) + "_" + String(info.options[j]));
- }
- } else {
- built_ins.push_back(String(info.name));
- }
- }
- }
-
- const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
-
- for (const String &E : built_ins) {
- syntax_highlighter->add_keyword_color(E, user_type_color);
- }
-
- // Colorize comments.
- const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
- syntax_highlighter->clear_color_regions();
- syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
- syntax_highlighter->add_color_region("//", "", comment_color, true);
- syntax_highlighter->set_disabled_branch_color(comment_color);
-
- text_editor->clear_comment_delimiters();
- text_editor->add_comment_delimiter("/*", "*/", false);
- text_editor->add_comment_delimiter("//", "", true);
-
- if (!text_editor->has_auto_brace_completion_open_key("/*")) {
- text_editor->add_auto_brace_completion_pair("/*", "*/");
- }
-
- // Colorize preprocessor include strings.
- const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
- syntax_highlighter->add_color_region("\"", "\"", string_color, false);
-
- if (warnings_panel) {
- // Warnings panel.
- warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts")));
- warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));
- }
-}
-
-void ShaderTextEditor::_check_shader_mode() {
- String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text());
-
- Shader::Mode mode;
-
- if (type == "canvas_item") {
- mode = Shader::MODE_CANVAS_ITEM;
- } else if (type == "particles") {
- mode = Shader::MODE_PARTICLES;
- } else if (type == "sky") {
- mode = Shader::MODE_SKY;
- } else if (type == "fog") {
- mode = Shader::MODE_FOG;
- } else {
- mode = Shader::MODE_SPATIAL;
- }
-
- if (shader->get_mode() != mode) {
- set_block_shader_changed(true);
- shader->set_code(get_text_editor()->get_text());
- set_block_shader_changed(false);
- _load_theme_settings();
- }
-}
-
-static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) {
- RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable);
- return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
-}
-
-static String complete_from_path;
-
-static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) {
- if (!p_efsd) {
- return;
- }
- for (int i = 0; i < p_efsd->get_file_count(); i++) {
- if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) {
- String path = p_efsd->get_file_path(i);
- if (path.begins_with(complete_from_path)) {
- path = path.replace_first(complete_from_path, "");
- }
- r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH));
- }
- }
- for (int j = 0; j < p_efsd->get_subdir_count(); j++) {
- _complete_include_paths_search(p_efsd->get_subdir(j), r_options);
- }
-}
-
-static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) {
- _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options);
-}
-
-void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {
- List<ScriptLanguage::CodeCompletionOption> pp_options;
- List<ScriptLanguage::CodeCompletionOption> pp_defines;
- ShaderPreprocessor preprocessor;
- String code;
- complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir();
- if (!complete_from_path.ends_with("/")) {
- complete_from_path += "/";
- }
- preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
- complete_from_path = String();
- if (pp_options.size()) {
- for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
- r_options->push_back(E);
- }
- return;
- }
- for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) {
- r_options->push_back(E);
- }
-
- ShaderLanguage sl;
- String calltip;
- ShaderLanguage::ShaderCompileInfo info;
- info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
-
- if (shader.is_null()) {
- info.is_include = true;
-
- sl.complete(code, info, r_options, calltip);
- get_text_editor()->set_code_hint(calltip);
- return;
- }
- _check_shader_mode();
- info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()));
- info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
- info.shader_types = ShaderTypes::get_singleton()->get_types();
-
- sl.complete(code, info, r_options, calltip);
- get_text_editor()->set_code_hint(calltip);
-}
-
-void ShaderTextEditor::_validate_script() {
- emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied
-
- String code;
-
- if (shader.is_valid()) {
- _check_shader_mode();
- code = shader->get_code();
- } else {
- code = shader_inc->get_code();
- }
-
- ShaderPreprocessor preprocessor;
- String code_pp;
- String error_pp;
- List<ShaderPreprocessor::FilePosition> err_positions;
- List<ShaderPreprocessor::Region> regions;
- String filename;
- if (shader.is_valid()) {
- filename = shader->get_path();
- } else if (shader_inc.is_valid()) {
- filename = shader_inc->get_path();
- }
- last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, &regions);
-
- for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
- get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
- }
-
- syntax_highlighter->clear_disabled_branch_regions();
- for (const ShaderPreprocessor::Region &region : regions) {
- if (!region.enabled) {
- if (filename != region.file) {
- continue;
- }
- syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line));
- }
- }
-
- set_error("");
- set_error_count(0);
-
- if (last_compile_result != OK) {
- //preprocessor error
- ERR_FAIL_COND(err_positions.size() == 0);
-
- String error_text = error_pp;
- int error_line = err_positions.front()->get().line;
- if (err_positions.size() == 1) {
- // Error in main file
- error_text = "error(" + itos(error_line) + "): " + error_text;
- } else {
- error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text;
- set_error_count(err_positions.size() - 1);
- }
-
- set_error(error_text);
- set_error_pos(error_line - 1, 0);
- for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
- get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
- }
- get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
-
- set_warning_count(0);
-
- } else {
- ShaderLanguage sl;
-
- sl.enable_warning_checking(saved_warnings_enabled);
- uint32_t flags = saved_warning_flags;
- if (shader.is_null()) {
- if (flags & ShaderWarning::UNUSED_CONSTANT) {
- flags &= ~(ShaderWarning::UNUSED_CONSTANT);
- }
- if (flags & ShaderWarning::UNUSED_FUNCTION) {
- flags &= ~(ShaderWarning::UNUSED_FUNCTION);
- }
- if (flags & ShaderWarning::UNUSED_STRUCT) {
- flags &= ~(ShaderWarning::UNUSED_STRUCT);
- }
- if (flags & ShaderWarning::UNUSED_UNIFORM) {
- flags &= ~(ShaderWarning::UNUSED_UNIFORM);
- }
- if (flags & ShaderWarning::UNUSED_VARYING) {
- flags &= ~(ShaderWarning::UNUSED_VARYING);
- }
- }
- sl.set_warning_flags(flags);
-
- ShaderLanguage::ShaderCompileInfo info;
- info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
-
- if (shader.is_null()) {
- info.is_include = true;
- } else {
- Shader::Mode mode = shader->get_mode();
- info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
- info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
- info.shader_types = ShaderTypes::get_singleton()->get_types();
- }
-
- code = code_pp;
- //compiler error
- last_compile_result = sl.compile(code, info);
-
- if (last_compile_result != OK) {
- String error_text;
- int error_line;
- Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
- if (include_positions.size() > 1) {
- //error is in an include
- error_line = include_positions[0].line;
- error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text();
- set_error_count(include_positions.size() - 1);
- } else {
- error_line = sl.get_error_line();
- error_text = "error(" + itos(error_line) + "): " + sl.get_error_text();
- set_error_count(0);
- }
- set_error(error_text);
- set_error_pos(error_line - 1, 0);
- get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
- } else {
- set_error("");
- }
-
- if (warnings.size() > 0 || last_compile_result != OK) {
- warnings_panel->clear();
- }
- warnings.clear();
- for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
- warnings.push_back(E->get());
- }
- if (warnings.size() > 0 && last_compile_result == OK) {
- warnings.sort_custom<WarningsComparator>();
- _update_warning_panel();
- } else {
- set_warning_count(0);
- }
- }
-
- emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts
-}
-
-void ShaderTextEditor::_update_warning_panel() {
- int warning_count = 0;
-
- warnings_panel->push_table(2);
- for (int i = 0; i < warnings.size(); i++) {
- ShaderWarning &w = warnings[i];
-
- if (warning_count == 0) {
- if (saved_treat_warning_as_errors) {
- String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
- set_error_pos(w.get_line() - 1, 0);
- set_error(error_text);
- get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
- }
- }
-
- warning_count++;
- int line = w.get_line();
-
- // First cell.
- warnings_panel->push_cell();
- warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor")));
- if (line != -1) {
- warnings_panel->push_meta(line - 1);
- warnings_panel->add_text(TTR("Line") + " " + itos(line));
- warnings_panel->add_text(" (" + w.get_name() + "):");
- warnings_panel->pop(); // Meta goto.
- } else {
- warnings_panel->add_text(w.get_name() + ":");
- }
- warnings_panel->pop(); // Color.
- warnings_panel->pop(); // Cell.
-
- // Second cell.
- warnings_panel->push_cell();
- warnings_panel->add_text(w.get_message());
- warnings_panel->pop(); // Cell.
- }
- warnings_panel->pop(); // Table.
-
- set_warning_count(warning_count);
-}
-
-void ShaderTextEditor::_bind_methods() {
- ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid")));
-}
-
-ShaderTextEditor::ShaderTextEditor() {
- syntax_highlighter.instantiate();
- get_text_editor()->set_syntax_highlighter(syntax_highlighter);
-}
-
-/*** SCRIPT EDITOR ******/
-
-void ShaderEditor::_menu_option(int p_option) {
- switch (p_option) {
- case EDIT_UNDO: {
- shader_editor->get_text_editor()->undo();
- } break;
- case EDIT_REDO: {
- shader_editor->get_text_editor()->redo();
- } break;
- case EDIT_CUT: {
- shader_editor->get_text_editor()->cut();
- } break;
- case EDIT_COPY: {
- shader_editor->get_text_editor()->copy();
- } break;
- case EDIT_PASTE: {
- shader_editor->get_text_editor()->paste();
- } break;
- case EDIT_SELECT_ALL: {
- shader_editor->get_text_editor()->select_all();
- } break;
- case EDIT_MOVE_LINE_UP: {
- shader_editor->move_lines_up();
- } break;
- case EDIT_MOVE_LINE_DOWN: {
- shader_editor->move_lines_down();
- } break;
- case EDIT_INDENT: {
- if (shader.is_null()) {
- return;
- }
- shader_editor->get_text_editor()->indent_lines();
- } break;
- case EDIT_UNINDENT: {
- if (shader.is_null()) {
- return;
- }
- shader_editor->get_text_editor()->unindent_lines();
- } break;
- case EDIT_DELETE_LINE: {
- shader_editor->delete_lines();
- } break;
- case EDIT_DUPLICATE_SELECTION: {
- shader_editor->duplicate_selection();
- } break;
- case EDIT_TOGGLE_COMMENT: {
- if (shader.is_null()) {
- return;
- }
-
- shader_editor->toggle_inline_comment("//");
-
- } break;
- case EDIT_COMPLETE: {
- shader_editor->get_text_editor()->request_code_completion();
- } break;
- case SEARCH_FIND: {
- shader_editor->get_find_replace_bar()->popup_search();
- } break;
- case SEARCH_FIND_NEXT: {
- shader_editor->get_find_replace_bar()->search_next();
- } break;
- case SEARCH_FIND_PREV: {
- shader_editor->get_find_replace_bar()->search_prev();
- } break;
- case SEARCH_REPLACE: {
- shader_editor->get_find_replace_bar()->popup_replace();
- } break;
- case SEARCH_GOTO_LINE: {
- goto_line_dialog->popup_find_line(shader_editor->get_text_editor());
- } break;
- case BOOKMARK_TOGGLE: {
- shader_editor->toggle_bookmark();
- } break;
- case BOOKMARK_GOTO_NEXT: {
- shader_editor->goto_next_bookmark();
- } break;
- case BOOKMARK_GOTO_PREV: {
- shader_editor->goto_prev_bookmark();
- } break;
- case BOOKMARK_REMOVE_ALL: {
- shader_editor->remove_all_bookmarks();
- } break;
- case HELP_DOCS: {
- OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL));
- } break;
- }
- if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
- shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus"));
- }
-}
-
-void ShaderEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED: {
- PopupMenu *popup = help_menu->get_popup();
- popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
- } break;
-
- case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
- _check_for_external_edit();
- } break;
- }
-}
-
-void ShaderEditor::_editor_settings_changed() {
- shader_editor->update_editor_settings();
-
- shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/appearance/whitespace/line_spacing"));
- shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false);
- shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
-}
-
-void ShaderEditor::_show_warnings_panel(bool p_show) {
- warnings_panel->set_visible(p_show);
-}
-
-void ShaderEditor::_warning_clicked(Variant p_line) {
- if (p_line.get_type() == Variant::INT) {
- shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t());
- }
-}
-
-void ShaderEditor::_bind_methods() {
- ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel);
- ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked);
-
- ADD_SIGNAL(MethodInfo("validation_changed"));
-}
-
-void ShaderEditor::ensure_select_current() {
-}
-
-void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
- shader_editor->goto_line_selection(p_line, p_begin, p_end);
-}
-
-void ShaderEditor::_project_settings_changed() {
- _update_warnings(true);
-}
-
-void ShaderEditor::_update_warnings(bool p_validate) {
- bool changed = false;
-
- bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
- if (warnings_enabled != saved_warnings_enabled) {
- saved_warnings_enabled = warnings_enabled;
- changed = true;
- }
-
- bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
- if (treat_warning_as_errors != saved_treat_warning_as_errors) {
- saved_treat_warning_as_errors = treat_warning_as_errors;
- changed = true;
- }
-
- bool update_flags = false;
-
- for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
- ShaderWarning::Code code = (ShaderWarning::Code)i;
- bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
-
- if (saved_warnings[code] != value) {
- saved_warnings[code] = value;
- update_flags = true;
- changed = true;
- }
- }
-
- if (update_flags) {
- saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
- }
-
- if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
- shader_editor->validate_script();
- }
-}
-
-void ShaderEditor::_check_for_external_edit() {
- bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"));
-
- if (shader_inc.is_valid()) {
- if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) {
- if (use_autoreload) {
- _reload_shader_include_from_disk();
- } else {
- disk_changed->call_deferred(SNAME("popup_centered"));
- }
- }
- return;
- }
-
- if (shader.is_null() || shader->is_built_in()) {
- return;
- }
-
- if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) {
- if (use_autoreload) {
- _reload_shader_from_disk();
- } else {
- disk_changed->call_deferred(SNAME("popup_centered"));
- }
- }
-}
-
-void ShaderEditor::_reload_shader_from_disk() {
- Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
- ERR_FAIL_COND(!rel_shader.is_valid());
-
- shader_editor->set_block_shader_changed(true);
- shader->set_code(rel_shader->get_code());
- shader_editor->set_block_shader_changed(false);
- shader->set_last_modified_time(rel_shader->get_last_modified_time());
- shader_editor->reload_text();
-}
-
-void ShaderEditor::_reload_shader_include_from_disk() {
- Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
- ERR_FAIL_COND(!rel_shader_include.is_valid());
-
- shader_editor->set_block_shader_changed(true);
- shader_inc->set_code(rel_shader_include->get_code());
- shader_editor->set_block_shader_changed(false);
- shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time());
- shader_editor->reload_text();
-}
-
-void ShaderEditor::_reload() {
- if (shader.is_valid()) {
- _reload_shader_from_disk();
- } else if (shader_inc.is_valid()) {
- _reload_shader_include_from_disk();
- }
-}
-
-void ShaderEditor::edit(const Ref<Shader> &p_shader) {
- if (p_shader.is_null() || !p_shader->is_text_shader()) {
- return;
- }
-
- if (shader == p_shader) {
- return;
- }
-
- shader = p_shader;
- shader_inc = Ref<ShaderInclude>();
-
- shader_editor->set_edited_shader(shader);
-}
-
-void ShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) {
- if (p_shader_inc.is_null()) {
- return;
- }
-
- if (shader_inc == p_shader_inc) {
- return;
- }
-
- shader_inc = p_shader_inc;
- shader = Ref<Shader>();
-
- shader_editor->set_edited_shader_include(p_shader_inc);
-}
-
-void ShaderEditor::save_external_data(const String &p_str) {
- if (shader.is_null() && shader_inc.is_null()) {
- disk_changed->hide();
- return;
- }
-
- apply_shaders();
-
- Ref<Shader> edited_shader = shader_editor->get_edited_shader();
- if (edited_shader.is_valid()) {
- ResourceSaver::save(edited_shader);
- }
- if (shader.is_valid() && shader != edited_shader) {
- ResourceSaver::save(shader);
- }
-
- Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include();
- if (edited_shader_inc.is_valid()) {
- ResourceSaver::save(edited_shader_inc);
- }
- if (shader_inc.is_valid() && shader_inc != edited_shader_inc) {
- ResourceSaver::save(shader_inc);
- }
- shader_editor->get_text_editor()->tag_saved_version();
-
- disk_changed->hide();
-}
-
-void ShaderEditor::validate_script() {
- shader_editor->_validate_script();
-}
-
-bool ShaderEditor::is_unsaved() const {
- return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version();
-}
-
-void ShaderEditor::apply_shaders() {
- String editor_code = shader_editor->get_text_editor()->get_text();
- if (shader.is_valid()) {
- String shader_code = shader->get_code();
- if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
- shader_editor->set_block_shader_changed(true);
- shader->set_code(editor_code);
- shader_editor->set_block_shader_changed(false);
- shader->set_edited(true);
- }
- }
- if (shader_inc.is_valid()) {
- String shader_inc_code = shader_inc->get_code();
- if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
- shader_editor->set_block_shader_changed(true);
- shader_inc->set_code(editor_code);
- shader_editor->set_block_shader_changed(false);
- shader_inc->set_edited(true);
- }
- }
-
- dependencies_version = shader_editor->get_dependencies_version();
-}
-
-void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
- Ref<InputEventMouseButton> mb = ev;
-
- if (mb.is_valid()) {
- if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
- CodeEdit *tx = shader_editor->get_text_editor();
-
- Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
- int row = pos.y;
- int col = pos.x;
- tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click"));
-
- if (tx->is_move_caret_on_right_click_enabled()) {
- if (tx->has_selection()) {
- int from_line = tx->get_selection_from_line();
- int to_line = tx->get_selection_to_line();
- int from_column = tx->get_selection_from_column();
- int to_column = tx->get_selection_to_column();
-
- if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
- // Right click is outside the selected text
- tx->deselect();
- }
- }
- if (!tx->has_selection()) {
- tx->set_caret_line(row, true, false);
- tx->set_caret_column(col);
- }
- }
- _make_context_menu(tx->has_selection(), get_local_mouse_position());
- }
- }
-
- Ref<InputEventKey> k = ev;
- if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
- CodeEdit *tx = shader_editor->get_text_editor();
- tx->adjust_viewport_to_caret();
- _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos()));
- context_menu->grab_focus();
- }
-}
-
-void ShaderEditor::_update_bookmark_list() {
- bookmarks_menu->clear();
-
- bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
- bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);
- bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);
- bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);
-
- PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines();
- if (bookmark_list.size() == 0) {
- return;
- }
-
- bookmarks_menu->add_separator();
-
- for (int i = 0; i < bookmark_list.size(); i++) {
- String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges();
- // Limit the size of the line if too big.
- if (line.length() > 50) {
- line = line.substr(0, 50);
- }
-
- bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\"");
- bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);
- }
-}
-
-void ShaderEditor::_bookmark_item_pressed(int p_idx) {
- if (p_idx < 4) { // Any item before the separator.
- _menu_option(bookmarks_menu->get_item_id(p_idx));
- } else {
- shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx));
- }
-}
-
-void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
- context_menu->clear();
- if (p_selection) {
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
- }
-
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
- context_menu->add_separator();
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
- context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
-
- context_menu->add_separator();
- context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
- context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
- context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
- context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
-
- context_menu->set_position(get_screen_position() + p_position);
- context_menu->reset_size();
- context_menu->popup();
-}
-
-ShaderEditor::ShaderEditor() {
- GLOBAL_DEF("debug/shader_language/warnings/enable", true);
- GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false);
- for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) {
- GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true);
- }
- _update_warnings(false);
-
- shader_editor = memnew(ShaderTextEditor);
-
- shader_editor->connect("script_validated", callable_mp(this, &ShaderEditor::_script_validated));
-
- shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
- shader_editor->add_theme_constant_override("separation", 0);
- shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
-
- shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel));
- shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders));
- EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed));
- ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed));
-
- shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
-
- shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
- shader_editor->get_text_editor()->set_context_menu_enabled(false);
- shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &ShaderEditor::_text_edit_gui_input));
-
- shader_editor->update_editor_settings();
-
- context_menu = memnew(PopupMenu);
- add_child(context_menu);
- context_menu->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
-
- VBoxContainer *main_container = memnew(VBoxContainer);
- HBoxContainer *hbc = memnew(HBoxContainer);
-
- edit_menu = memnew(MenuButton);
- edit_menu->set_shortcut_context(this);
- edit_menu->set_text(TTR("Edit"));
- edit_menu->set_switch_on_hover(true);
-
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
- edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
- edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
- edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
- edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
- edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
-
- search_menu = memnew(MenuButton);
- search_menu->set_shortcut_context(this);
- search_menu->set_text(TTR("Search"));
- search_menu->set_switch_on_hover(true);
-
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
- search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
-
- MenuButton *goto_menu = memnew(MenuButton);
- goto_menu->set_shortcut_context(this);
- goto_menu->set_text(TTR("Go To"));
- goto_menu->set_switch_on_hover(true);
- goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
-
- goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
- goto_menu->get_popup()->add_separator();
-
- bookmarks_menu = memnew(PopupMenu);
- bookmarks_menu->set_name("Bookmarks");
- goto_menu->get_popup()->add_child(bookmarks_menu);
- goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
- _update_bookmark_list();
- bookmarks_menu->connect("about_to_popup", callable_mp(this, &ShaderEditor::_update_bookmark_list));
- bookmarks_menu->connect("index_pressed", callable_mp(this, &ShaderEditor::_bookmark_item_pressed));
-
- help_menu = memnew(MenuButton);
- help_menu->set_text(TTR("Help"));
- help_menu->set_switch_on_hover(true);
- help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS);
- help_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
-
- add_child(main_container);
- main_container->add_child(hbc);
- hbc->add_child(search_menu);
- hbc->add_child(edit_menu);
- hbc->add_child(goto_menu);
- hbc->add_child(help_menu);
- hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles")));
-
- VSplitContainer *editor_box = memnew(VSplitContainer);
- main_container->add_child(editor_box);
- editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
- editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
- editor_box->add_child(shader_editor);
-
- FindReplaceBar *bar = memnew(FindReplaceBar);
- main_container->add_child(bar);
- bar->hide();
- shader_editor->set_find_replace_bar(bar);
-
- warnings_panel = memnew(RichTextLabel);
- warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
- warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
- warnings_panel->set_meta_underline(true);
- warnings_panel->set_selection_enabled(true);
- warnings_panel->set_focus_mode(FOCUS_CLICK);
- warnings_panel->hide();
- warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked));
- editor_box->add_child(warnings_panel);
- shader_editor->set_warnings_panel(warnings_panel);
-
- goto_line_dialog = memnew(GotoLineDialog);
- add_child(goto_line_dialog);
-
- disk_changed = memnew(ConfirmationDialog);
-
- VBoxContainer *vbc = memnew(VBoxContainer);
- disk_changed->add_child(vbc);
-
- Label *dl = memnew(Label);
- dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?"));
- vbc->add_child(dl);
-
- disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload));
- disk_changed->set_ok_button_text(TTR("Reload"));
-
- disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
- disk_changed->connect("custom_action", callable_mp(this, &ShaderEditor::save_external_data));
-
- add_child(disk_changed);
-
- _editor_settings_changed();
-}
void ShaderEditorPlugin::_update_shader_list() {
shader_list->clear();
@@ -1241,7 +81,7 @@ void ShaderEditorPlugin::_update_shader_list() {
shader_list->select(shader_tabs->get_current_tab());
}
- for (int i = 1; i < FILE_MAX; i++) {
+ for (int i = FILE_SAVE; i < FILE_MAX; i++) {
file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.size() == 0);
}
@@ -1250,7 +90,7 @@ void ShaderEditorPlugin::_update_shader_list() {
void ShaderEditorPlugin::_update_shader_list_status() {
for (int i = 0; i < shader_list->get_item_count(); i++) {
- ShaderEditor *se = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(i));
+ TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i));
if (se) {
if (se->was_compilation_successful()) {
shader_list->set_item_tag_icon(i, Ref<Texture2D>());
@@ -1285,7 +125,7 @@ void ShaderEditorPlugin::edit(Object *p_object) {
}
}
es.shader_inc = Ref<ShaderInclude>(si);
- es.shader_editor = memnew(ShaderEditor);
+ es.shader_editor = memnew(TextShaderEditor);
es.shader_editor->edit(si);
shader_tabs->add_child(es.shader_editor);
es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
@@ -1305,7 +145,7 @@ void ShaderEditorPlugin::edit(Object *p_object) {
shader_tabs->add_child(es.visual_shader_editor);
es.visual_shader_editor->edit(vs.ptr());
} else {
- es.shader_editor = memnew(ShaderEditor);
+ es.shader_editor = memnew(TextShaderEditor);
shader_tabs->add_child(es.shader_editor);
es.shader_editor->edit(s);
es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
@@ -1330,7 +170,7 @@ void ShaderEditorPlugin::make_visible(bool p_visible) {
void ShaderEditorPlugin::selected_notify() {
}
-ShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) {
+TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) {
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
if (edited_shaders[i].shader == p_for_shader) {
return edited_shaders[i].shader_editor;
@@ -1592,7 +432,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
file_hb->add_child(file_menu);
- for (int i = 2; i < FILE_MAX; i++) {
+ for (int i = FILE_SAVE; i < FILE_MAX; i++) {
file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true);
}
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index 5188639bfc..7638778af7 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -31,181 +31,14 @@
#ifndef SHADER_EDITOR_PLUGIN_H
#define SHADER_EDITOR_PLUGIN_H
-#include "editor/code_editor.h"
#include "editor/editor_plugin.h"
-#include "scene/gui/menu_button.h"
-#include "scene/gui/panel_container.h"
-#include "scene/gui/rich_text_label.h"
-#include "scene/gui/tab_container.h"
-#include "scene/gui/text_edit.h"
-#include "scene/main/timer.h"
-#include "scene/resources/shader.h"
-#include "scene/resources/shader_include.h"
-#include "servers/rendering/shader_warnings.h"
-class ItemList;
-class VisualShaderEditor;
class HSplitContainer;
+class ItemList;
class ShaderCreateDialog;
-
-class GDShaderSyntaxHighlighter : public CodeHighlighter {
- GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter)
-
-private:
- Vector<Point2i> disabled_branch_regions;
- Color disabled_branch_color;
-
-public:
- virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override;
-
- void add_disabled_branch_region(const Point2i &p_region);
- void clear_disabled_branch_regions();
- void set_disabled_branch_color(const Color &p_color);
-};
-
-class ShaderTextEditor : public CodeTextEditor {
- GDCLASS(ShaderTextEditor, CodeTextEditor);
-
- Color marked_line_color = Color(1, 1, 1);
-
- struct WarningsComparator {
- _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); }
- };
-
- Ref<GDShaderSyntaxHighlighter> syntax_highlighter;
- RichTextLabel *warnings_panel = nullptr;
- Ref<Shader> shader;
- Ref<ShaderInclude> shader_inc;
- List<ShaderWarning> warnings;
- Error last_compile_result = Error::OK;
-
- void _check_shader_mode();
- void _update_warning_panel();
-
- bool block_shader_changed = false;
- void _shader_changed();
-
- uint32_t dependencies_version = 0; // Incremented if deps changed
-
-protected:
- void _notification(int p_what);
- static void _bind_methods();
- virtual void _load_theme_settings() override;
-
- virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override;
-
-public:
- void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; }
- uint32_t get_dependencies_version() const { return dependencies_version; }
-
- virtual void _validate_script() override;
-
- void reload_text();
- void set_warnings_panel(RichTextLabel *p_warnings_panel);
-
- Ref<Shader> get_edited_shader() const;
- Ref<ShaderInclude> get_edited_shader_include() const;
-
- void set_edited_shader(const Ref<Shader> &p_shader);
- void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code);
- void set_edited_shader_include(const Ref<ShaderInclude> &p_include);
- void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code);
- void set_edited_code(const String &p_code);
-
- ShaderTextEditor();
-};
-
-class ShaderEditor : public MarginContainer {
- GDCLASS(ShaderEditor, MarginContainer);
-
- enum {
- EDIT_UNDO,
- EDIT_REDO,
- EDIT_CUT,
- EDIT_COPY,
- EDIT_PASTE,
- EDIT_SELECT_ALL,
- EDIT_MOVE_LINE_UP,
- EDIT_MOVE_LINE_DOWN,
- EDIT_INDENT,
- EDIT_UNINDENT,
- EDIT_DELETE_LINE,
- EDIT_DUPLICATE_SELECTION,
- EDIT_TOGGLE_COMMENT,
- EDIT_COMPLETE,
- SEARCH_FIND,
- SEARCH_FIND_NEXT,
- SEARCH_FIND_PREV,
- SEARCH_REPLACE,
- SEARCH_GOTO_LINE,
- BOOKMARK_TOGGLE,
- BOOKMARK_GOTO_NEXT,
- BOOKMARK_GOTO_PREV,
- BOOKMARK_REMOVE_ALL,
- HELP_DOCS,
- };
-
- MenuButton *edit_menu = nullptr;
- MenuButton *search_menu = nullptr;
- PopupMenu *bookmarks_menu = nullptr;
- MenuButton *help_menu = nullptr;
- PopupMenu *context_menu = nullptr;
- RichTextLabel *warnings_panel = nullptr;
- uint64_t idle = 0;
-
- GotoLineDialog *goto_line_dialog = nullptr;
- ConfirmationDialog *erase_tab_confirm = nullptr;
- ConfirmationDialog *disk_changed = nullptr;
-
- ShaderTextEditor *shader_editor = nullptr;
- bool compilation_success = true;
-
- void _menu_option(int p_option);
- mutable Ref<Shader> shader;
- mutable Ref<ShaderInclude> shader_inc;
-
- void _editor_settings_changed();
- void _project_settings_changed();
-
- void _check_for_external_edit();
- void _reload_shader_from_disk();
- void _reload_shader_include_from_disk();
- void _reload();
- void _show_warnings_panel(bool p_show);
- void _warning_clicked(Variant p_line);
- void _update_warnings(bool p_validate);
-
- void _script_validated(bool p_valid) {
- compilation_success = p_valid;
- emit_signal(SNAME("validation_changed"));
- }
-
- uint32_t dependencies_version = 0xFFFFFFFF;
-
-protected:
- void _notification(int p_what);
- static void _bind_methods();
- void _make_context_menu(bool p_selection, Vector2 p_position);
- void _text_edit_gui_input(const Ref<InputEvent> &p_ev);
-
- void _update_bookmark_list();
- void _bookmark_item_pressed(int p_idx);
-
-public:
- bool was_compilation_successful() const { return compilation_success; }
- void apply_shaders();
- void ensure_select_current();
- void edit(const Ref<Shader> &p_shader);
- void edit(const Ref<ShaderInclude> &p_shader_inc);
- void goto_line_selection(int p_line, int p_begin, int p_end);
- void save_external_data(const String &p_str = "");
- void validate_script();
- bool is_unsaved() const;
-
- virtual Size2 get_minimum_size() const override { return Size2(0, 200); }
-
- ShaderEditor();
-};
+class TabContainer;
+class TextShaderEditor;
+class VisualShaderEditor;
class ShaderEditorPlugin : public EditorPlugin {
GDCLASS(ShaderEditorPlugin, EditorPlugin);
@@ -213,12 +46,14 @@ class ShaderEditorPlugin : public EditorPlugin {
struct EditedShader {
Ref<Shader> shader;
Ref<ShaderInclude> shader_inc;
- ShaderEditor *shader_editor = nullptr;
+ TextShaderEditor *shader_editor = nullptr;
VisualShaderEditor *visual_shader_editor = nullptr;
};
LocalVector<EditedShader> edited_shaders;
+ // Always valid operations come first in the enum, file-specific ones
+ // should go after FILE_SAVE which is used to build the menu accordingly.
enum {
FILE_NEW,
FILE_NEW_INCLUDE,
@@ -265,7 +100,7 @@ public:
virtual void make_visible(bool p_visible) override;
virtual void selected_notify() override;
- ShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader);
+ TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader);
VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader);
virtual void save_external_data() override;
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index 20809763d0..9846cd4a84 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -150,6 +150,7 @@ void TextEditor::reload_text() {
te->tag_saved_version();
code_editor->update_line_and_column();
+ _validate_script();
}
void TextEditor::_validate_script() {
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
new file mode 100644
index 0000000000..dfd5e76ba6
--- /dev/null
+++ b/editor/plugins/text_shader_editor.cpp
@@ -0,0 +1,1191 @@
+/*************************************************************************/
+/* text_shader_editor.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 "text_shader_editor.h"
+
+#include "core/version_generated.gen.h"
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+#include "editor/filesystem_dock.h"
+#include "editor/project_settings_editor.h"
+#include "scene/gui/split_container.h"
+#include "servers/rendering/shader_preprocessor.h"
+#include "servers/rendering/shader_types.h"
+
+/*** SHADER SYNTAX HIGHLIGHTER ****/
+
+Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
+ Dictionary color_map;
+
+ for (const Point2i &region : disabled_branch_regions) {
+ if (p_line >= region.x && p_line <= region.y) {
+ Dictionary highlighter_info;
+ highlighter_info["color"] = disabled_branch_color;
+
+ color_map[0] = highlighter_info;
+ return color_map;
+ }
+ }
+
+ return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line);
+}
+
+void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) {
+ ERR_FAIL_COND(p_region.x < 0);
+ ERR_FAIL_COND(p_region.y < 0);
+
+ for (int i = 0; i < disabled_branch_regions.size(); i++) {
+ ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists.");
+ }
+
+ Point2i disabled_branch_region;
+ disabled_branch_region.x = p_region.x;
+ disabled_branch_region.y = p_region.y;
+ disabled_branch_regions.push_back(disabled_branch_region);
+
+ clear_highlighting_cache();
+}
+
+void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() {
+ disabled_branch_regions.clear();
+ clear_highlighting_cache();
+}
+
+void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) {
+ disabled_branch_color = p_color;
+ clear_highlighting_cache();
+}
+
+/*** SHADER SCRIPT EDITOR ****/
+
+static bool saved_warnings_enabled = false;
+static bool saved_treat_warning_as_errors = false;
+static HashMap<ShaderWarning::Code, bool> saved_warnings;
+static uint32_t saved_warning_flags = 0U;
+
+void ShaderTextEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ if (is_visible_in_tree()) {
+ _load_theme_settings();
+ if (warnings.size() > 0 && last_compile_result == OK) {
+ warnings_panel->clear();
+ _update_warning_panel();
+ }
+ }
+ } break;
+ }
+}
+
+Ref<Shader> ShaderTextEditor::get_edited_shader() const {
+ return shader;
+}
+
+Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const {
+ return shader_inc;
+}
+
+void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) {
+ set_edited_shader(p_shader, p_shader->get_code());
+}
+
+void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) {
+ if (shader == p_shader) {
+ return;
+ }
+ if (shader.is_valid()) {
+ shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+ }
+ shader = p_shader;
+ shader_inc = Ref<ShaderInclude>();
+
+ set_edited_code(p_code);
+
+ if (shader.is_valid()) {
+ shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+ }
+}
+
+void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
+ set_edited_shader_include(p_shader_inc, p_shader_inc->get_code());
+}
+
+void ShaderTextEditor::_shader_changed() {
+ // This function is used for dependencies (include changing changes main shader and forces it to revalidate)
+ if (block_shader_changed) {
+ return;
+ }
+ dependencies_version++;
+ _validate_script();
+}
+
+void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) {
+ if (shader_inc == p_shader_inc) {
+ return;
+ }
+ if (shader_inc.is_valid()) {
+ shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+ }
+ shader_inc = p_shader_inc;
+ shader = Ref<Shader>();
+
+ set_edited_code(p_code);
+
+ if (shader_inc.is_valid()) {
+ shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+ }
+}
+
+void ShaderTextEditor::set_edited_code(const String &p_code) {
+ _load_theme_settings();
+
+ get_text_editor()->set_text(p_code);
+ get_text_editor()->clear_undo_history();
+ get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0);
+ get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0);
+ get_text_editor()->tag_saved_version();
+
+ _validate_script();
+ _line_col_changed();
+}
+
+void ShaderTextEditor::reload_text() {
+ ERR_FAIL_COND(shader.is_null());
+
+ CodeEdit *te = get_text_editor();
+ int column = te->get_caret_column();
+ int row = te->get_caret_line();
+ int h = te->get_h_scroll();
+ int v = te->get_v_scroll();
+
+ te->set_text(shader->get_code());
+ te->set_caret_line(row);
+ te->set_caret_column(column);
+ te->set_h_scroll(h);
+ te->set_v_scroll(v);
+
+ te->tag_saved_version();
+
+ update_line_and_column();
+}
+
+void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
+ warnings_panel = p_warnings_panel;
+}
+
+void ShaderTextEditor::_load_theme_settings() {
+ CodeEdit *text_editor = get_text_editor();
+ Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color");
+ if (updated_marked_line_color != marked_line_color) {
+ for (int i = 0; i < text_editor->get_line_count(); i++) {
+ if (text_editor->get_line_background_color(i) == marked_line_color) {
+ text_editor->set_line_background_color(i, updated_marked_line_color);
+ }
+ }
+ marked_line_color = updated_marked_line_color;
+ }
+
+ syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
+ syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
+ syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
+ syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
+
+ syntax_highlighter->clear_keyword_colors();
+
+ const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
+ const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
+
+ List<String> keywords;
+ ShaderLanguage::get_keyword_list(&keywords);
+
+ for (const String &E : keywords) {
+ if (ShaderLanguage::is_control_flow_keyword(E)) {
+ syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
+ } else {
+ syntax_highlighter->add_keyword_color(E, keyword_color);
+ }
+ }
+
+ List<String> pp_keywords;
+ ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
+
+ for (const String &E : pp_keywords) {
+ syntax_highlighter->add_keyword_color(E, keyword_color);
+ }
+
+ // Colorize built-ins like `COLOR` differently to make them easier
+ // to distinguish from keywords at a quick glance.
+
+ List<String> built_ins;
+
+ if (shader_inc.is_valid()) {
+ for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
+ for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) {
+ for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
+ built_ins.push_back(F.key);
+ }
+ }
+
+ const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+
+ for (int j = 0; j < modes.size(); j++) {
+ const ShaderLanguage::ModeInfo &info = modes[j];
+
+ if (!info.options.is_empty()) {
+ for (int k = 0; k < info.options.size(); k++) {
+ built_ins.push_back(String(info.name) + "_" + String(info.options[k]));
+ }
+ } else {
+ built_ins.push_back(String(info.name));
+ }
+ }
+ }
+ } else if (shader.is_valid()) {
+ for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) {
+ for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
+ built_ins.push_back(F.key);
+ }
+ }
+
+ const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+
+ for (int i = 0; i < modes.size(); i++) {
+ const ShaderLanguage::ModeInfo &info = modes[i];
+
+ if (!info.options.is_empty()) {
+ for (int j = 0; j < info.options.size(); j++) {
+ built_ins.push_back(String(info.name) + "_" + String(info.options[j]));
+ }
+ } else {
+ built_ins.push_back(String(info.name));
+ }
+ }
+ }
+
+ const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
+
+ for (const String &E : built_ins) {
+ syntax_highlighter->add_keyword_color(E, user_type_color);
+ }
+
+ // Colorize comments.
+ const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
+ syntax_highlighter->clear_color_regions();
+ syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
+ syntax_highlighter->add_color_region("//", "", comment_color, true);
+ syntax_highlighter->set_disabled_branch_color(comment_color);
+
+ text_editor->clear_comment_delimiters();
+ text_editor->add_comment_delimiter("/*", "*/", false);
+ text_editor->add_comment_delimiter("//", "", true);
+
+ if (!text_editor->has_auto_brace_completion_open_key("/*")) {
+ text_editor->add_auto_brace_completion_pair("/*", "*/");
+ }
+
+ // Colorize preprocessor include strings.
+ const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ syntax_highlighter->add_color_region("\"", "\"", string_color, false);
+
+ if (warnings_panel) {
+ // Warnings panel.
+ warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts")));
+ warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));
+ }
+}
+
+void ShaderTextEditor::_check_shader_mode() {
+ String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text());
+
+ Shader::Mode mode;
+
+ if (type == "canvas_item") {
+ mode = Shader::MODE_CANVAS_ITEM;
+ } else if (type == "particles") {
+ mode = Shader::MODE_PARTICLES;
+ } else if (type == "sky") {
+ mode = Shader::MODE_SKY;
+ } else if (type == "fog") {
+ mode = Shader::MODE_FOG;
+ } else {
+ mode = Shader::MODE_SPATIAL;
+ }
+
+ if (shader->get_mode() != mode) {
+ set_block_shader_changed(true);
+ shader->set_code(get_text_editor()->get_text());
+ set_block_shader_changed(false);
+ _load_theme_settings();
+ }
+}
+
+static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) {
+ RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable);
+ return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
+}
+
+static String complete_from_path;
+
+static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) {
+ if (!p_efsd) {
+ return;
+ }
+ for (int i = 0; i < p_efsd->get_file_count(); i++) {
+ if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) {
+ String path = p_efsd->get_file_path(i);
+ if (path.begins_with(complete_from_path)) {
+ path = path.replace_first(complete_from_path, "");
+ }
+ r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH));
+ }
+ }
+ for (int j = 0; j < p_efsd->get_subdir_count(); j++) {
+ _complete_include_paths_search(p_efsd->get_subdir(j), r_options);
+ }
+}
+
+static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) {
+ _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options);
+}
+
+void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {
+ List<ScriptLanguage::CodeCompletionOption> pp_options;
+ List<ScriptLanguage::CodeCompletionOption> pp_defines;
+ ShaderPreprocessor preprocessor;
+ String code;
+ complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir();
+ if (!complete_from_path.ends_with("/")) {
+ complete_from_path += "/";
+ }
+ preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
+ complete_from_path = String();
+ if (pp_options.size()) {
+ for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
+ r_options->push_back(E);
+ }
+ return;
+ }
+ for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) {
+ r_options->push_back(E);
+ }
+
+ ShaderLanguage sl;
+ String calltip;
+ ShaderLanguage::ShaderCompileInfo info;
+ info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
+
+ if (shader.is_null()) {
+ info.is_include = true;
+
+ sl.complete(code, info, r_options, calltip);
+ get_text_editor()->set_code_hint(calltip);
+ return;
+ }
+ _check_shader_mode();
+ info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()));
+ info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+ info.shader_types = ShaderTypes::get_singleton()->get_types();
+
+ sl.complete(code, info, r_options, calltip);
+ get_text_editor()->set_code_hint(calltip);
+}
+
+void ShaderTextEditor::_validate_script() {
+ emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied
+
+ String code;
+
+ if (shader.is_valid()) {
+ _check_shader_mode();
+ code = shader->get_code();
+ } else {
+ code = shader_inc->get_code();
+ }
+
+ ShaderPreprocessor preprocessor;
+ String code_pp;
+ String error_pp;
+ List<ShaderPreprocessor::FilePosition> err_positions;
+ List<ShaderPreprocessor::Region> regions;
+ String filename;
+ if (shader.is_valid()) {
+ filename = shader->get_path();
+ } else if (shader_inc.is_valid()) {
+ filename = shader_inc->get_path();
+ }
+ last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, &regions);
+
+ for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
+ get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
+ }
+
+ syntax_highlighter->clear_disabled_branch_regions();
+ for (const ShaderPreprocessor::Region &region : regions) {
+ if (!region.enabled) {
+ if (filename != region.file) {
+ continue;
+ }
+ syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line));
+ }
+ }
+
+ set_error("");
+ set_error_count(0);
+
+ if (last_compile_result != OK) {
+ //preprocessor error
+ ERR_FAIL_COND(err_positions.size() == 0);
+
+ String error_text = error_pp;
+ int error_line = err_positions.front()->get().line;
+ if (err_positions.size() == 1) {
+ // Error in main file
+ error_text = "error(" + itos(error_line) + "): " + error_text;
+ } else {
+ error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text;
+ set_error_count(err_positions.size() - 1);
+ }
+
+ set_error(error_text);
+ set_error_pos(error_line - 1, 0);
+ for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
+ get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
+ }
+ get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
+
+ set_warning_count(0);
+
+ } else {
+ ShaderLanguage sl;
+
+ sl.enable_warning_checking(saved_warnings_enabled);
+ uint32_t flags = saved_warning_flags;
+ if (shader.is_null()) {
+ if (flags & ShaderWarning::UNUSED_CONSTANT) {
+ flags &= ~(ShaderWarning::UNUSED_CONSTANT);
+ }
+ if (flags & ShaderWarning::UNUSED_FUNCTION) {
+ flags &= ~(ShaderWarning::UNUSED_FUNCTION);
+ }
+ if (flags & ShaderWarning::UNUSED_STRUCT) {
+ flags &= ~(ShaderWarning::UNUSED_STRUCT);
+ }
+ if (flags & ShaderWarning::UNUSED_UNIFORM) {
+ flags &= ~(ShaderWarning::UNUSED_UNIFORM);
+ }
+ if (flags & ShaderWarning::UNUSED_VARYING) {
+ flags &= ~(ShaderWarning::UNUSED_VARYING);
+ }
+ }
+ sl.set_warning_flags(flags);
+
+ ShaderLanguage::ShaderCompileInfo info;
+ info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
+
+ if (shader.is_null()) {
+ info.is_include = true;
+ } else {
+ Shader::Mode mode = shader->get_mode();
+ info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
+ info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
+ info.shader_types = ShaderTypes::get_singleton()->get_types();
+ }
+
+ code = code_pp;
+ //compiler error
+ last_compile_result = sl.compile(code, info);
+
+ if (last_compile_result != OK) {
+ String error_text;
+ int error_line;
+ Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
+ if (include_positions.size() > 1) {
+ //error is in an include
+ error_line = include_positions[0].line;
+ error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text();
+ set_error_count(include_positions.size() - 1);
+ } else {
+ error_line = sl.get_error_line();
+ error_text = "error(" + itos(error_line) + "): " + sl.get_error_text();
+ set_error_count(0);
+ }
+ set_error(error_text);
+ set_error_pos(error_line - 1, 0);
+ get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
+ } else {
+ set_error("");
+ }
+
+ if (warnings.size() > 0 || last_compile_result != OK) {
+ warnings_panel->clear();
+ }
+ warnings.clear();
+ for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
+ warnings.push_back(E->get());
+ }
+ if (warnings.size() > 0 && last_compile_result == OK) {
+ warnings.sort_custom<WarningsComparator>();
+ _update_warning_panel();
+ } else {
+ set_warning_count(0);
+ }
+ }
+
+ emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts
+}
+
+void ShaderTextEditor::_update_warning_panel() {
+ int warning_count = 0;
+
+ warnings_panel->push_table(2);
+ for (int i = 0; i < warnings.size(); i++) {
+ ShaderWarning &w = warnings[i];
+
+ if (warning_count == 0) {
+ if (saved_treat_warning_as_errors) {
+ String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
+ set_error_pos(w.get_line() - 1, 0);
+ set_error(error_text);
+ get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
+ }
+ }
+
+ warning_count++;
+ int line = w.get_line();
+
+ // First cell.
+ warnings_panel->push_cell();
+ warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor")));
+ if (line != -1) {
+ warnings_panel->push_meta(line - 1);
+ warnings_panel->add_text(TTR("Line") + " " + itos(line));
+ warnings_panel->add_text(" (" + w.get_name() + "):");
+ warnings_panel->pop(); // Meta goto.
+ } else {
+ warnings_panel->add_text(w.get_name() + ":");
+ }
+ warnings_panel->pop(); // Color.
+ warnings_panel->pop(); // Cell.
+
+ // Second cell.
+ warnings_panel->push_cell();
+ warnings_panel->add_text(w.get_message());
+ warnings_panel->pop(); // Cell.
+ }
+ warnings_panel->pop(); // Table.
+
+ set_warning_count(warning_count);
+}
+
+void ShaderTextEditor::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid")));
+}
+
+ShaderTextEditor::ShaderTextEditor() {
+ syntax_highlighter.instantiate();
+ get_text_editor()->set_syntax_highlighter(syntax_highlighter);
+}
+
+/*** SCRIPT EDITOR ******/
+
+void TextShaderEditor::_menu_option(int p_option) {
+ switch (p_option) {
+ case EDIT_UNDO: {
+ shader_editor->get_text_editor()->undo();
+ } break;
+ case EDIT_REDO: {
+ shader_editor->get_text_editor()->redo();
+ } break;
+ case EDIT_CUT: {
+ shader_editor->get_text_editor()->cut();
+ } break;
+ case EDIT_COPY: {
+ shader_editor->get_text_editor()->copy();
+ } break;
+ case EDIT_PASTE: {
+ shader_editor->get_text_editor()->paste();
+ } break;
+ case EDIT_SELECT_ALL: {
+ shader_editor->get_text_editor()->select_all();
+ } break;
+ case EDIT_MOVE_LINE_UP: {
+ shader_editor->move_lines_up();
+ } break;
+ case EDIT_MOVE_LINE_DOWN: {
+ shader_editor->move_lines_down();
+ } break;
+ case EDIT_INDENT: {
+ if (shader.is_null()) {
+ return;
+ }
+ shader_editor->get_text_editor()->indent_lines();
+ } break;
+ case EDIT_UNINDENT: {
+ if (shader.is_null()) {
+ return;
+ }
+ shader_editor->get_text_editor()->unindent_lines();
+ } break;
+ case EDIT_DELETE_LINE: {
+ shader_editor->delete_lines();
+ } break;
+ case EDIT_DUPLICATE_SELECTION: {
+ shader_editor->duplicate_selection();
+ } break;
+ case EDIT_TOGGLE_COMMENT: {
+ if (shader.is_null()) {
+ return;
+ }
+
+ shader_editor->toggle_inline_comment("//");
+
+ } break;
+ case EDIT_COMPLETE: {
+ shader_editor->get_text_editor()->request_code_completion();
+ } break;
+ case SEARCH_FIND: {
+ shader_editor->get_find_replace_bar()->popup_search();
+ } break;
+ case SEARCH_FIND_NEXT: {
+ shader_editor->get_find_replace_bar()->search_next();
+ } break;
+ case SEARCH_FIND_PREV: {
+ shader_editor->get_find_replace_bar()->search_prev();
+ } break;
+ case SEARCH_REPLACE: {
+ shader_editor->get_find_replace_bar()->popup_replace();
+ } break;
+ case SEARCH_GOTO_LINE: {
+ goto_line_dialog->popup_find_line(shader_editor->get_text_editor());
+ } break;
+ case BOOKMARK_TOGGLE: {
+ shader_editor->toggle_bookmark();
+ } break;
+ case BOOKMARK_GOTO_NEXT: {
+ shader_editor->goto_next_bookmark();
+ } break;
+ case BOOKMARK_GOTO_PREV: {
+ shader_editor->goto_prev_bookmark();
+ } break;
+ case BOOKMARK_REMOVE_ALL: {
+ shader_editor->remove_all_bookmarks();
+ } break;
+ case HELP_DOCS: {
+ OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL));
+ } break;
+ }
+ if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
+ shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus"));
+ }
+}
+
+void TextShaderEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ PopupMenu *popup = help_menu->get_popup();
+ popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
+ } break;
+
+ case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
+ _check_for_external_edit();
+ } break;
+ }
+}
+
+void TextShaderEditor::_editor_settings_changed() {
+ shader_editor->update_editor_settings();
+
+ shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/appearance/whitespace/line_spacing"));
+ shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false);
+ shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
+}
+
+void TextShaderEditor::_show_warnings_panel(bool p_show) {
+ warnings_panel->set_visible(p_show);
+}
+
+void TextShaderEditor::_warning_clicked(Variant p_line) {
+ if (p_line.get_type() == Variant::INT) {
+ shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t());
+ }
+}
+
+void TextShaderEditor::_bind_methods() {
+ ClassDB::bind_method("_show_warnings_panel", &TextShaderEditor::_show_warnings_panel);
+ ClassDB::bind_method("_warning_clicked", &TextShaderEditor::_warning_clicked);
+
+ ADD_SIGNAL(MethodInfo("validation_changed"));
+}
+
+void TextShaderEditor::ensure_select_current() {
+}
+
+void TextShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
+ shader_editor->goto_line_selection(p_line, p_begin, p_end);
+}
+
+void TextShaderEditor::_project_settings_changed() {
+ _update_warnings(true);
+}
+
+void TextShaderEditor::_update_warnings(bool p_validate) {
+ bool changed = false;
+
+ bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
+ if (warnings_enabled != saved_warnings_enabled) {
+ saved_warnings_enabled = warnings_enabled;
+ changed = true;
+ }
+
+ bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
+ if (treat_warning_as_errors != saved_treat_warning_as_errors) {
+ saved_treat_warning_as_errors = treat_warning_as_errors;
+ changed = true;
+ }
+
+ bool update_flags = false;
+
+ for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
+ ShaderWarning::Code code = (ShaderWarning::Code)i;
+ bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
+
+ if (saved_warnings[code] != value) {
+ saved_warnings[code] = value;
+ update_flags = true;
+ changed = true;
+ }
+ }
+
+ if (update_flags) {
+ saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
+ }
+
+ if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
+ shader_editor->validate_script();
+ }
+}
+
+void TextShaderEditor::_check_for_external_edit() {
+ bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"));
+
+ if (shader_inc.is_valid()) {
+ if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) {
+ if (use_autoreload) {
+ _reload_shader_include_from_disk();
+ } else {
+ disk_changed->call_deferred(SNAME("popup_centered"));
+ }
+ }
+ return;
+ }
+
+ if (shader.is_null() || shader->is_built_in()) {
+ return;
+ }
+
+ if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) {
+ if (use_autoreload) {
+ _reload_shader_from_disk();
+ } else {
+ disk_changed->call_deferred(SNAME("popup_centered"));
+ }
+ }
+}
+
+void TextShaderEditor::_reload_shader_from_disk() {
+ Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
+ ERR_FAIL_COND(!rel_shader.is_valid());
+
+ shader_editor->set_block_shader_changed(true);
+ shader->set_code(rel_shader->get_code());
+ shader_editor->set_block_shader_changed(false);
+ shader->set_last_modified_time(rel_shader->get_last_modified_time());
+ shader_editor->reload_text();
+}
+
+void TextShaderEditor::_reload_shader_include_from_disk() {
+ Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
+ ERR_FAIL_COND(!rel_shader_include.is_valid());
+
+ shader_editor->set_block_shader_changed(true);
+ shader_inc->set_code(rel_shader_include->get_code());
+ shader_editor->set_block_shader_changed(false);
+ shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time());
+ shader_editor->reload_text();
+}
+
+void TextShaderEditor::_reload() {
+ if (shader.is_valid()) {
+ _reload_shader_from_disk();
+ } else if (shader_inc.is_valid()) {
+ _reload_shader_include_from_disk();
+ }
+}
+
+void TextShaderEditor::edit(const Ref<Shader> &p_shader) {
+ if (p_shader.is_null() || !p_shader->is_text_shader()) {
+ return;
+ }
+
+ if (shader == p_shader) {
+ return;
+ }
+
+ shader = p_shader;
+ shader_inc = Ref<ShaderInclude>();
+
+ shader_editor->set_edited_shader(shader);
+}
+
+void TextShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) {
+ if (p_shader_inc.is_null()) {
+ return;
+ }
+
+ if (shader_inc == p_shader_inc) {
+ return;
+ }
+
+ shader_inc = p_shader_inc;
+ shader = Ref<Shader>();
+
+ shader_editor->set_edited_shader_include(p_shader_inc);
+}
+
+void TextShaderEditor::save_external_data(const String &p_str) {
+ if (shader.is_null() && shader_inc.is_null()) {
+ disk_changed->hide();
+ return;
+ }
+
+ apply_shaders();
+
+ Ref<Shader> edited_shader = shader_editor->get_edited_shader();
+ if (edited_shader.is_valid()) {
+ ResourceSaver::save(edited_shader);
+ }
+ if (shader.is_valid() && shader != edited_shader) {
+ ResourceSaver::save(shader);
+ }
+
+ Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include();
+ if (edited_shader_inc.is_valid()) {
+ ResourceSaver::save(edited_shader_inc);
+ }
+ if (shader_inc.is_valid() && shader_inc != edited_shader_inc) {
+ ResourceSaver::save(shader_inc);
+ }
+ shader_editor->get_text_editor()->tag_saved_version();
+
+ disk_changed->hide();
+}
+
+void TextShaderEditor::validate_script() {
+ shader_editor->_validate_script();
+}
+
+bool TextShaderEditor::is_unsaved() const {
+ return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version();
+}
+
+void TextShaderEditor::apply_shaders() {
+ String editor_code = shader_editor->get_text_editor()->get_text();
+ if (shader.is_valid()) {
+ String shader_code = shader->get_code();
+ if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
+ shader_editor->set_block_shader_changed(true);
+ shader->set_code(editor_code);
+ shader_editor->set_block_shader_changed(false);
+ shader->set_edited(true);
+ }
+ }
+ if (shader_inc.is_valid()) {
+ String shader_inc_code = shader_inc->get_code();
+ if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
+ shader_editor->set_block_shader_changed(true);
+ shader_inc->set_code(editor_code);
+ shader_editor->set_block_shader_changed(false);
+ shader_inc->set_edited(true);
+ }
+ }
+
+ dependencies_version = shader_editor->get_dependencies_version();
+}
+
+void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
+ Ref<InputEventMouseButton> mb = ev;
+
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
+ CodeEdit *tx = shader_editor->get_text_editor();
+
+ Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
+ int row = pos.y;
+ int col = pos.x;
+ tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click"));
+
+ if (tx->is_move_caret_on_right_click_enabled()) {
+ if (tx->has_selection()) {
+ int from_line = tx->get_selection_from_line();
+ int to_line = tx->get_selection_to_line();
+ int from_column = tx->get_selection_from_column();
+ int to_column = tx->get_selection_to_column();
+
+ if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
+ // Right click is outside the selected text
+ tx->deselect();
+ }
+ }
+ if (!tx->has_selection()) {
+ tx->set_caret_line(row, true, false);
+ tx->set_caret_column(col);
+ }
+ }
+ _make_context_menu(tx->has_selection(), get_local_mouse_position());
+ }
+ }
+
+ Ref<InputEventKey> k = ev;
+ if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
+ CodeEdit *tx = shader_editor->get_text_editor();
+ tx->adjust_viewport_to_caret();
+ _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos()));
+ context_menu->grab_focus();
+ }
+}
+
+void TextShaderEditor::_update_bookmark_list() {
+ bookmarks_menu->clear();
+
+ bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
+ bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);
+ bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);
+ bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);
+
+ PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines();
+ if (bookmark_list.size() == 0) {
+ return;
+ }
+
+ bookmarks_menu->add_separator();
+
+ for (int i = 0; i < bookmark_list.size(); i++) {
+ String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges();
+ // Limit the size of the line if too big.
+ if (line.length() > 50) {
+ line = line.substr(0, 50);
+ }
+
+ bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\"");
+ bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);
+ }
+}
+
+void TextShaderEditor::_bookmark_item_pressed(int p_idx) {
+ if (p_idx < 4) { // Any item before the separator.
+ _menu_option(bookmarks_menu->get_item_id(p_idx));
+ } else {
+ shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx));
+ }
+}
+
+void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
+ context_menu->clear();
+ if (p_selection) {
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
+ }
+
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
+ context_menu->add_separator();
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
+
+ context_menu->add_separator();
+ context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
+ context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
+
+ context_menu->set_position(get_screen_position() + p_position);
+ context_menu->reset_size();
+ context_menu->popup();
+}
+
+TextShaderEditor::TextShaderEditor() {
+ GLOBAL_DEF("debug/shader_language/warnings/enable", true);
+ GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false);
+ for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) {
+ GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true);
+ }
+ _update_warnings(false);
+
+ shader_editor = memnew(ShaderTextEditor);
+
+ shader_editor->connect("script_validated", callable_mp(this, &TextShaderEditor::_script_validated));
+
+ shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ shader_editor->add_theme_constant_override("separation", 0);
+ shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
+
+ shader_editor->connect("show_warnings_panel", callable_mp(this, &TextShaderEditor::_show_warnings_panel));
+ shader_editor->connect("script_changed", callable_mp(this, &TextShaderEditor::apply_shaders));
+ EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_editor_settings_changed));
+ ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &TextShaderEditor::_project_settings_changed));
+
+ shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
+
+ shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
+ shader_editor->get_text_editor()->set_context_menu_enabled(false);
+ shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &TextShaderEditor::_text_edit_gui_input));
+
+ shader_editor->update_editor_settings();
+
+ context_menu = memnew(PopupMenu);
+ add_child(context_menu);
+ context_menu->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
+
+ VBoxContainer *main_container = memnew(VBoxContainer);
+ HBoxContainer *hbc = memnew(HBoxContainer);
+
+ edit_menu = memnew(MenuButton);
+ edit_menu->set_shortcut_context(this);
+ edit_menu->set_text(TTR("Edit"));
+ edit_menu->set_switch_on_hover(true);
+
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
+ edit_menu->get_popup()->add_separator();
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
+ edit_menu->get_popup()->add_separator();
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
+ edit_menu->get_popup()->add_separator();
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_separator();
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
+ edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
+
+ search_menu = memnew(MenuButton);
+ search_menu->set_shortcut_context(this);
+ search_menu->set_text(TTR("Search"));
+ search_menu->set_switch_on_hover(true);
+
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
+ search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
+
+ MenuButton *goto_menu = memnew(MenuButton);
+ goto_menu->set_shortcut_context(this);
+ goto_menu->set_text(TTR("Go To"));
+ goto_menu->set_switch_on_hover(true);
+ goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
+
+ goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
+ goto_menu->get_popup()->add_separator();
+
+ bookmarks_menu = memnew(PopupMenu);
+ bookmarks_menu->set_name("Bookmarks");
+ goto_menu->get_popup()->add_child(bookmarks_menu);
+ goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
+ _update_bookmark_list();
+ bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list));
+ bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed));
+
+ help_menu = memnew(MenuButton);
+ help_menu->set_text(TTR("Help"));
+ help_menu->set_switch_on_hover(true);
+ help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS);
+ help_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
+
+ add_child(main_container);
+ main_container->add_child(hbc);
+ hbc->add_child(search_menu);
+ hbc->add_child(edit_menu);
+ hbc->add_child(goto_menu);
+ hbc->add_child(help_menu);
+ hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles")));
+
+ VSplitContainer *editor_box = memnew(VSplitContainer);
+ main_container->add_child(editor_box);
+ editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
+ editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
+ editor_box->add_child(shader_editor);
+
+ FindReplaceBar *bar = memnew(FindReplaceBar);
+ main_container->add_child(bar);
+ bar->hide();
+ shader_editor->set_find_replace_bar(bar);
+
+ warnings_panel = memnew(RichTextLabel);
+ warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+ warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ warnings_panel->set_meta_underline(true);
+ warnings_panel->set_selection_enabled(true);
+ warnings_panel->set_focus_mode(FOCUS_CLICK);
+ warnings_panel->hide();
+ warnings_panel->connect("meta_clicked", callable_mp(this, &TextShaderEditor::_warning_clicked));
+ editor_box->add_child(warnings_panel);
+ shader_editor->set_warnings_panel(warnings_panel);
+
+ goto_line_dialog = memnew(GotoLineDialog);
+ add_child(goto_line_dialog);
+
+ disk_changed = memnew(ConfirmationDialog);
+
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ disk_changed->add_child(vbc);
+
+ Label *dl = memnew(Label);
+ dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?"));
+ vbc->add_child(dl);
+
+ disk_changed->connect("confirmed", callable_mp(this, &TextShaderEditor::_reload));
+ disk_changed->set_ok_button_text(TTR("Reload"));
+
+ disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
+ disk_changed->connect("custom_action", callable_mp(this, &TextShaderEditor::save_external_data));
+
+ add_child(disk_changed);
+
+ _editor_settings_changed();
+}
diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h
new file mode 100644
index 0000000000..abeaff1fff
--- /dev/null
+++ b/editor/plugins/text_shader_editor.h
@@ -0,0 +1,199 @@
+/*************************************************************************/
+/* text_shader_editor.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 TEXT_SHADER_EDITOR_H
+#define TEXT_SHADER_EDITOR_H
+
+#include "editor/code_editor.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
+#include "servers/rendering/shader_warnings.h"
+
+class GDShaderSyntaxHighlighter : public CodeHighlighter {
+ GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter)
+
+private:
+ Vector<Point2i> disabled_branch_regions;
+ Color disabled_branch_color;
+
+public:
+ virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override;
+
+ void add_disabled_branch_region(const Point2i &p_region);
+ void clear_disabled_branch_regions();
+ void set_disabled_branch_color(const Color &p_color);
+};
+
+class ShaderTextEditor : public CodeTextEditor {
+ GDCLASS(ShaderTextEditor, CodeTextEditor);
+
+ Color marked_line_color = Color(1, 1, 1);
+
+ struct WarningsComparator {
+ _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); }
+ };
+
+ Ref<GDShaderSyntaxHighlighter> syntax_highlighter;
+ RichTextLabel *warnings_panel = nullptr;
+ Ref<Shader> shader;
+ Ref<ShaderInclude> shader_inc;
+ List<ShaderWarning> warnings;
+ Error last_compile_result = Error::OK;
+
+ void _check_shader_mode();
+ void _update_warning_panel();
+
+ bool block_shader_changed = false;
+ void _shader_changed();
+
+ uint32_t dependencies_version = 0; // Incremented if deps changed
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+ virtual void _load_theme_settings() override;
+
+ virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override;
+
+public:
+ void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; }
+ uint32_t get_dependencies_version() const { return dependencies_version; }
+
+ virtual void _validate_script() override;
+
+ void reload_text();
+ void set_warnings_panel(RichTextLabel *p_warnings_panel);
+
+ Ref<Shader> get_edited_shader() const;
+ Ref<ShaderInclude> get_edited_shader_include() const;
+
+ void set_edited_shader(const Ref<Shader> &p_shader);
+ void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code);
+ void set_edited_shader_include(const Ref<ShaderInclude> &p_include);
+ void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code);
+ void set_edited_code(const String &p_code);
+
+ ShaderTextEditor();
+};
+
+class TextShaderEditor : public MarginContainer {
+ GDCLASS(TextShaderEditor, MarginContainer);
+
+ enum {
+ EDIT_UNDO,
+ EDIT_REDO,
+ EDIT_CUT,
+ EDIT_COPY,
+ EDIT_PASTE,
+ EDIT_SELECT_ALL,
+ EDIT_MOVE_LINE_UP,
+ EDIT_MOVE_LINE_DOWN,
+ EDIT_INDENT,
+ EDIT_UNINDENT,
+ EDIT_DELETE_LINE,
+ EDIT_DUPLICATE_SELECTION,
+ EDIT_TOGGLE_COMMENT,
+ EDIT_COMPLETE,
+ SEARCH_FIND,
+ SEARCH_FIND_NEXT,
+ SEARCH_FIND_PREV,
+ SEARCH_REPLACE,
+ SEARCH_GOTO_LINE,
+ BOOKMARK_TOGGLE,
+ BOOKMARK_GOTO_NEXT,
+ BOOKMARK_GOTO_PREV,
+ BOOKMARK_REMOVE_ALL,
+ HELP_DOCS,
+ };
+
+ MenuButton *edit_menu = nullptr;
+ MenuButton *search_menu = nullptr;
+ PopupMenu *bookmarks_menu = nullptr;
+ MenuButton *help_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
+ RichTextLabel *warnings_panel = nullptr;
+ uint64_t idle = 0;
+
+ GotoLineDialog *goto_line_dialog = nullptr;
+ ConfirmationDialog *erase_tab_confirm = nullptr;
+ ConfirmationDialog *disk_changed = nullptr;
+
+ ShaderTextEditor *shader_editor = nullptr;
+ bool compilation_success = true;
+
+ void _menu_option(int p_option);
+ mutable Ref<Shader> shader;
+ mutable Ref<ShaderInclude> shader_inc;
+
+ void _editor_settings_changed();
+ void _project_settings_changed();
+
+ void _check_for_external_edit();
+ void _reload_shader_from_disk();
+ void _reload_shader_include_from_disk();
+ void _reload();
+ void _show_warnings_panel(bool p_show);
+ void _warning_clicked(Variant p_line);
+ void _update_warnings(bool p_validate);
+
+ void _script_validated(bool p_valid) {
+ compilation_success = p_valid;
+ emit_signal(SNAME("validation_changed"));
+ }
+
+ uint32_t dependencies_version = 0xFFFFFFFF;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+ void _make_context_menu(bool p_selection, Vector2 p_position);
+ void _text_edit_gui_input(const Ref<InputEvent> &p_ev);
+
+ void _update_bookmark_list();
+ void _bookmark_item_pressed(int p_idx);
+
+public:
+ bool was_compilation_successful() const { return compilation_success; }
+ void apply_shaders();
+ void ensure_select_current();
+ void edit(const Ref<Shader> &p_shader);
+ void edit(const Ref<ShaderInclude> &p_shader_inc);
+ void goto_line_selection(int p_line, int p_begin, int p_end);
+ void save_external_data(const String &p_str = "");
+ void validate_script();
+ bool is_unsaved() const;
+
+ virtual Size2 get_minimum_size() const override { return Size2(0, 200); }
+
+ TextShaderEditor();
+};
+
+#endif // TEXT_SHADER_EDITOR_H
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 395c9b0248..acfa8b3d00 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -2428,20 +2428,16 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_line(Vector2i
return HashMap<Vector2i, TileMapCell>();
}
- if (selected_type == SELECTED_TYPE_CONNECT) {
- return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, true);
- } else if (selected_type == SELECTED_TYPE_PATH) {
- return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, false);
- } else { // SELECTED_TYPE_PATTERN
- TileSet::TerrainsPattern terrains_pattern;
- if (p_erase) {
- terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set);
- } else {
- terrains_pattern = selected_terrains_pattern;
+ if (p_erase) {
+ return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set));
+ } else {
+ if (selected_type == SELECTED_TYPE_CONNECT) {
+ return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, true);
+ } else if (selected_type == SELECTED_TYPE_PATH) {
+ return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, false);
+ } else { // SELECTED_TYPE_PATTERN
+ return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrains_pattern);
}
-
- Vector<Vector2i> line = TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell);
- return _draw_terrain_pattern(line, selected_terrain_set, terrains_pattern);
}
}
@@ -2468,16 +2464,14 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_rect(Vector2i
}
}
- if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) {
- return _draw_terrain_path_or_connect(to_draw, selected_terrain_set, selected_terrain, true);
- } else { // SELECTED_TYPE_PATTERN
- TileSet::TerrainsPattern terrains_pattern;
- if (p_erase) {
- terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set);
- } else {
- terrains_pattern = selected_terrains_pattern;
+ if (p_erase) {
+ return _draw_terrain_pattern(to_draw, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set));
+ } else {
+ if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) {
+ return _draw_terrain_path_or_connect(to_draw, selected_terrain_set, selected_terrain, true);
+ } else { // SELECTED_TYPE_PATTERN
+ return _draw_terrain_pattern(to_draw, selected_terrain_set, selected_terrains_pattern);
}
- return _draw_terrain_pattern(to_draw, selected_terrain_set, terrains_pattern);
}
}
@@ -2609,16 +2603,14 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_bucket_fill(Ve
cells_to_draw_as_vector.append(cell);
}
- if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) {
- return _draw_terrain_path_or_connect(cells_to_draw_as_vector, selected_terrain_set, selected_terrain, true);
- } else { // SELECTED_TYPE_PATTERN
- TileSet::TerrainsPattern terrains_pattern;
- if (p_erase) {
- terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set);
- } else {
- terrains_pattern = selected_terrains_pattern;
+ if (p_erase) {
+ return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set));
+ } else {
+ if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) {
+ return _draw_terrain_path_or_connect(cells_to_draw_as_vector, selected_terrain_set, selected_terrain, true);
+ } else { // SELECTED_TYPE_PATTERN
+ return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, selected_terrains_pattern);
}
- return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, terrains_pattern);
}
}
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index 80a8318bbb..7394288fcd 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -329,6 +329,7 @@ void TileSetEditor::_set_source_sort(int p_sort) {
}
}
_update_sources_list(old_selected);
+ EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_source_sort", p_sort);
}
void TileSetEditor::_notification(int p_what) {
@@ -648,7 +649,12 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
// Add the listener again.
if (tile_set.is_valid()) {
tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
- _update_sources_list();
+ if (first_edit) {
+ first_edit = false;
+ _set_source_sort(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_source_sort", 0));
+ } else {
+ _update_sources_list();
+ }
_update_patterns_list();
}
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
index 3b9b80dac4..290c53b109 100644
--- a/editor/plugins/tiles/tile_set_editor.h
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -83,6 +83,8 @@ private:
AtlasMergingDialog *atlas_merging_dialog = nullptr;
TileProxiesManagerDialog *tile_proxies_manager_dialog = nullptr;
+ bool first_edit = true;
+
// Patterns.
ItemList *patterns_item_list = nullptr;
Label *patterns_help_label = nullptr;
diff --git a/main/main.cpp b/main/main.cpp
index 28a73ce585..5c2259bf7c 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1706,13 +1706,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (rtm >= 0 && rtm < 3) {
-#ifdef NO_THREADS
- rtm = OS::RENDER_THREAD_UNSAFE; // No threads available on this platform.
-#else
if (editor) {
rtm = OS::RENDER_THREAD_SAFE;
}
-#endif
OS::get_singleton()->_render_thread_mode = OS::RenderThreadMode(rtm);
}
@@ -1932,11 +1928,9 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
// Print engine name and version
print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE));
-#if !defined(NO_THREADS)
if (p_main_tid_override) {
Thread::main_thread_id = p_main_tid_override;
}
-#endif
#ifdef TOOLS_ENABLED
if (editor || project_manager || cmdline_tool) {
diff --git a/methods.py b/methods.py
index dadac37cb5..7feffb2848 100644
--- a/methods.py
+++ b/methods.py
@@ -1,4 +1,5 @@
import os
+import sys
import re
import glob
import subprocess
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index e0deea1106..8b27307d0c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -43,26 +43,28 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
String prev_text = "";
int prev_column = 0;
-
bool prev_is_char = false;
bool prev_is_digit = false;
bool prev_is_binary_op = false;
+
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
- bool in_function_name = false;
- bool in_lambda = false;
- bool in_variable_declaration = false;
- bool in_signal_declaration = false;
- bool in_function_args = false;
- bool in_member_variable = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
bool in_string_name = false;
bool is_hex_notation = false;
bool is_bin_notation = false;
+ bool in_member_variable = false;
+ bool in_lambda = false;
+
+ bool in_function_name = false;
+ bool in_function_args = false;
+ bool in_variable_declaration = false;
+ bool in_signal_declaration = false;
bool expect_type = false;
+
Color keyword_color;
Color color;
@@ -241,16 +243,21 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
}
- // A bit of a hack, but couldn't come up with anything better.
+ // VERY hacky... but couldn't come up with anything better.
if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~' || str[j] == '.')) {
- if (prev_text == "true" || prev_text == "false" || prev_text == "self" || prev_text == "null" || prev_text == "PI" || prev_text == "TAU" || prev_text == "INF" || prev_text == "NAN") {
- is_binary_op = true;
- } else if (!keywords.has(prev_text)) {
- int k = j - 1;
- while (k > 0 && is_whitespace(str[k])) {
- k--;
- }
- if (!is_symbol(str[k]) || str[k] == '"' || str[k] == '\'' || str[k] == ')' || str[k] == ']' || str[k] == '}') {
+ int to = j - 1;
+ // Find what the last text was (prev_text won't work if there's no whitespace, so we need to do it manually).
+ while (to > 0 && is_whitespace(str[to])) {
+ to--;
+ }
+ int from = to;
+ while (from > 0 && !is_symbol(str[from])) {
+ from--;
+ }
+ String word = str.substr(from + 1, to - from);
+ // Keywords need to be exceptions, except for keywords that represent a value.
+ if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !keywords.has(word)) {
+ if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
is_binary_op = true;
}
}
@@ -285,16 +292,18 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
is_hex_notation = true;
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
- !((str[j] == 'e' || str[j] == '.') && (prev_is_digit || str[j - 1] == '_')) &&
+ !(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
+ !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !prev_is_binary_op && str[j - 1] != 'e')) {
- /* 1st row of condition: '+' or '-' after scientific notation;
- 2nd row of condition: '_' as a numeric separator;
- 3rd row of condition: Scientific notation 'e' and floating points;
- 4th row of condition: Multiple unary operators. */
+ /* This condition continues Number highlighting in special cases.
+ 1st row: '+' or '-' after scientific notation;
+ 2nd row: '_' as a numeric separator;
+ 3rd row: Scientific notation 'e' and floating points;
+ 4th row: Floating points inside the number, or leading if after a unary mathematical operator;
+ 5th row: Multiple unary mathematical operators */
in_number = false;
}
- } else if ((str[j] == '-' || str[j] == '+' || str[j] == '~' || (str[j] == '.' && str[j + 1] != '.' && (j == 0 || (j > 0 && str[j - 1] != '.')))) && !is_binary_op) {
- // Start a number from unary mathematical operators and floating points, except for '..'
+ } else if (!is_binary_op && (str[j] == '-' || str[j] == '+' || str[j] == '~' || (str[j] == '.' && str[j + 1] != '.' && (j == 0 || (j > 0 && str[j - 1] != '.'))))) {
in_number = true;
}
@@ -316,7 +325,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
Color col = Color();
if (global_functions.has(word)) {
// "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
- if (word == "assert" || word == "preload") {
+ if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
col = global_function_color;
} else {
// For other global functions, check if followed by bracket.
@@ -406,11 +415,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_function_args = false;
}
- if (expect_type && (prev_is_char || str[j] == '=')) {
+ if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') {
expect_type = false;
}
- if (j > 0 && str[j] == '>' && str[j - 1] == '-') {
+ if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
expect_type = true;
}
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index ab52761e17..f79731dd22 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -261,7 +261,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li
#define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE));
- ADD_OPTION_ENUM("blender/nodes/visible", "Visible Only,Renderable,All", BLEND_VISIBLE_ALL);
+ ADD_OPTION_ENUM("blender/nodes/visible", "All,Visible Only,Renderable", BLEND_VISIBLE_ALL);
ADD_OPTION_BOOL("blender/nodes/punctual_lights", true);
ADD_OPTION_BOOL("blender/nodes/cameras", true);
ADD_OPTION_BOOL("blender/nodes/custom_properties", true);
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index dd1c1b9889..a1485ff82e 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -45,9 +45,9 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
public:
enum {
+ BLEND_VISIBLE_ALL,
BLEND_VISIBLE_VISIBLE_ONLY,
- BLEND_VISIBLE_RENDERABLE,
- BLEND_VISIBLE_ALL
+ BLEND_VISIBLE_RENDERABLE
};
enum {
BLEND_BONE_INFLUENCES_NONE,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index fbd59d649f..9c3bc51c44 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -4,6 +4,21 @@ using System.Runtime.InteropServices;
namespace Godot
{
/// <summary>
+ /// Specifies which order Euler angle rotations should be in.
+ /// When composing, the order is the same as the letters. When decomposing,
+ /// the order is reversed (ex: YXZ decomposes Z first, then X, and Y last).
+ /// </summary>
+ public enum EulerOrder
+ {
+ XYZ,
+ XZY,
+ YXZ,
+ YZX,
+ ZXY,
+ ZYX
+ };
+
+ /// <summary>
/// 3×3 matrix used for 3D rotation and scale.
/// Almost always used as an orthogonal basis for a Transform.
///
@@ -244,50 +259,258 @@ namespace Godot
}
/// <summary>
- /// Returns the basis's rotation in the form of Euler angles
- /// (in the YXZ convention: when *decomposing*, first Z, then X, and Y last).
- /// The returned vector contains the rotation angles in
- /// the format (X angle, Y angle, Z angle).
+ /// Returns the basis's rotation in the form of Euler angles.
+ /// The Euler order depends on the [param order] parameter,
+ /// by default it uses the YXZ convention: when decomposing,
+ /// first Z, then X, and Y last. The returned vector contains
+ /// the rotation angles in the format (X angle, Y angle, Z angle).
///
/// Consider using the <see cref="GetRotationQuaternion"/> method instead, which
/// returns a <see cref="Quaternion"/> quaternion instead of Euler angles.
/// </summary>
+ /// <param name="order">The Euler order to use. By default, use YXZ order (most common).</param>
/// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns>
- public Vector3 GetEuler()
+ public Vector3 GetEuler(EulerOrder order = EulerOrder.YXZ)
{
- Basis m = Orthonormalized();
-
- Vector3 euler;
- euler.z = 0.0f;
-
- real_t mzy = m.Row1[2];
-
- if (mzy < 1.0f)
+ switch (order)
{
- if (mzy > -1.0f)
+ case EulerOrder.XYZ:
{
- euler.x = Mathf.Asin(-mzy);
- euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]);
- euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]);
+ // Euler angles in XYZ convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz -cy*sz sy
+ // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx
+ // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy
+ Vector3 euler;
+ real_t sy = Row0[2];
+ if (sy < (1.0f - Mathf.Epsilon))
+ {
+ if (sy > -(1.0f - Mathf.Epsilon))
+ {
+ // is this a pure Y rotation?
+ if (Row1[0] == 0 && Row0[1] == 0 && Row1[2] == 0 && Row2[1] == 0 && Row1[1] == 1)
+ {
+ // return the simplest form (human friendlier in editor and scripts)
+ euler.x = 0;
+ euler.y = Mathf.Atan2(Row0[2], Row0[0]);
+ euler.z = 0;
+ }
+ else
+ {
+ euler.x = Mathf.Atan2(-Row1[2], Row2[2]);
+ euler.y = Mathf.Asin(sy);
+ euler.z = Mathf.Atan2(-Row0[1], Row0[0]);
+ }
+ }
+ else
+ {
+ euler.x = Mathf.Atan2(Row2[1], Row1[1]);
+ euler.y = -Mathf.Tau / 4.0f;
+ euler.z = 0.0f;
+ }
+ }
+ else
+ {
+ euler.x = Mathf.Atan2(Row2[1], Row1[1]);
+ euler.y = Mathf.Tau / 4.0f;
+ euler.z = 0.0f;
+ }
+ return euler;
}
- else
+ case EulerOrder.XZY:
{
- euler.x = Mathf.Pi * 0.5f;
- euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
+ // Euler angles in XZY convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy -sz cz*sy
+ // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx
+ // cy*sx*sz cz*sx cx*cy+sx*sz*sy
+ Vector3 euler;
+ real_t sz = Row0[1];
+ if (sz < (1.0f - Mathf.Epsilon))
+ {
+ if (sz > -(1.0f - Mathf.Epsilon))
+ {
+ euler.x = Mathf.Atan2(Row2[1], Row1[1]);
+ euler.y = Mathf.Atan2(Row0[2], Row0[0]);
+ euler.z = Mathf.Asin(-sz);
+ }
+ else
+ {
+ // It's -1
+ euler.x = -Mathf.Atan2(Row1[2], Row2[2]);
+ euler.y = 0.0f;
+ euler.z = Mathf.Tau / 4.0f;
+ }
+ }
+ else
+ {
+ // It's 1
+ euler.x = -Mathf.Atan2(Row1[2], Row2[2]);
+ euler.y = 0.0f;
+ euler.z = -Mathf.Tau / 4.0f;
+ }
+ return euler;
}
- }
- else
- {
- euler.x = -Mathf.Pi * 0.5f;
- euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
- }
+ case EulerOrder.YXZ:
+ {
+ // Euler angles in YXZ convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy
+ // cx*sz cx*cz -sx
+ // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx
+ Vector3 euler;
+ real_t m12 = Row1[2];
+ if (m12 < (1 - Mathf.Epsilon))
+ {
+ if (m12 > -(1 - Mathf.Epsilon))
+ {
+ // is this a pure X rotation?
+ if (Row1[0] == 0 && Row0[1] == 0 && Row0[2] == 0 && Row2[0] == 0 && Row0[0] == 1)
+ {
+ // return the simplest form (human friendlier in editor and scripts)
+ euler.x = Mathf.Atan2(-m12, Row1[1]);
+ euler.y = 0;
+ euler.z = 0;
+ }
+ else
+ {
+ euler.x = Mathf.Asin(-m12);
+ euler.y = Mathf.Atan2(Row0[2], Row2[2]);
+ euler.z = Mathf.Atan2(Row1[0], Row1[1]);
+ }
+ }
+ else
+ { // m12 == -1
+ euler.x = Mathf.Tau / 4.0f;
+ euler.y = Mathf.Atan2(Row0[1], Row0[0]);
+ euler.z = 0;
+ }
+ }
+ else
+ { // m12 == 1
+ euler.x = -Mathf.Tau / 4.0f;
+ euler.y = -Mathf.Atan2(Row0[1], Row0[0]);
+ euler.z = 0;
+ }
- return euler;
+ return euler;
+ }
+ case EulerOrder.YZX:
+ {
+ // Euler angles in YZX convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx
+ // sz cz*cx -cz*sx
+ // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx
+ Vector3 euler;
+ real_t sz = Row1[0];
+ if (sz < (1.0f - Mathf.Epsilon))
+ {
+ if (sz > -(1.0f - Mathf.Epsilon))
+ {
+ euler.x = Mathf.Atan2(-Row1[2], Row1[1]);
+ euler.y = Mathf.Atan2(-Row2[0], Row0[0]);
+ euler.z = Mathf.Asin(sz);
+ }
+ else
+ {
+ // It's -1
+ euler.x = Mathf.Atan2(Row2[1], Row2[2]);
+ euler.y = 0.0f;
+ euler.z = -Mathf.Tau / 4.0f;
+ }
+ }
+ else
+ {
+ // It's 1
+ euler.x = Mathf.Atan2(Row2[1], Row2[2]);
+ euler.y = 0.0f;
+ euler.z = Mathf.Tau / 4.0f;
+ }
+ return euler;
+ }
+ case EulerOrder.ZXY:
+ {
+ // Euler angles in ZXY convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx
+ // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx
+ // -cx*sy sx cx*cy
+ Vector3 euler;
+ real_t sx = Row2[1];
+ if (sx < (1.0f - Mathf.Epsilon))
+ {
+ if (sx > -(1.0f - Mathf.Epsilon))
+ {
+ euler.x = Mathf.Asin(sx);
+ euler.y = Mathf.Atan2(-Row2[0], Row2[2]);
+ euler.z = Mathf.Atan2(-Row0[1], Row1[1]);
+ }
+ else
+ {
+ // It's -1
+ euler.x = -Mathf.Tau / 4.0f;
+ euler.y = Mathf.Atan2(Row0[2], Row0[0]);
+ euler.z = 0;
+ }
+ }
+ else
+ {
+ // It's 1
+ euler.x = Mathf.Tau / 4.0f;
+ euler.y = Mathf.Atan2(Row0[2], Row0[0]);
+ euler.z = 0;
+ }
+ return euler;
+ }
+ case EulerOrder.ZYX:
+ {
+ // Euler angles in ZYX convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy
+ // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx
+ // -sy cy*sx cy*cx
+ Vector3 euler;
+ real_t sy = Row2[0];
+ if (sy < (1.0f - Mathf.Epsilon))
+ {
+ if (sy > -(1.0f - Mathf.Epsilon))
+ {
+ euler.x = Mathf.Atan2(Row2[1], Row2[2]);
+ euler.y = Mathf.Asin(-sy);
+ euler.z = Mathf.Atan2(Row1[0], Row0[0]);
+ }
+ else
+ {
+ // It's -1
+ euler.x = 0;
+ euler.y = Mathf.Tau / 4.0f;
+ euler.z = -Mathf.Atan2(Row0[1], Row1[1]);
+ }
+ }
+ else
+ {
+ // It's 1
+ euler.x = 0;
+ euler.y = -Mathf.Tau / 4.0f;
+ euler.z = -Mathf.Atan2(Row0[1], Row1[1]);
+ }
+ return euler;
+ }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(order));
+ }
}
/// <summary>
/// Returns the basis's rotation in the form of a quaternion.
- /// See <see cref="GetEuler()"/> if you need Euler angles, but keep in
+ /// See <see cref="GetEuler"/> if you need Euler angles, but keep in
/// mind that quaternions should generally be preferred to Euler angles.
/// </summary>
/// <returns>A <see cref="Quaternion"/> representing the basis's rotation.</returns>
@@ -712,35 +935,6 @@ namespace Godot
}
/// <summary>
- /// Constructs a pure rotation basis matrix from the given Euler angles
- /// (in the YXZ convention: when *composing*, first Y, then X, and Z last),
- /// given in the vector format as (X angle, Y angle, Z angle).
- ///
- /// Consider using the <see cref="Basis(Quaternion)"/> constructor instead, which
- /// uses a <see cref="Quaternion"/> quaternion instead of Euler angles.
- /// </summary>
- /// <param name="eulerYXZ">The Euler angles to create the basis from.</param>
- public Basis(Vector3 eulerYXZ)
- {
- real_t c;
- real_t s;
-
- c = Mathf.Cos(eulerYXZ.x);
- s = Mathf.Sin(eulerYXZ.x);
- var xmat = new Basis(1, 0, 0, 0, c, -s, 0, s, c);
-
- c = Mathf.Cos(eulerYXZ.y);
- s = Mathf.Sin(eulerYXZ.y);
- var ymat = new Basis(c, 0, s, 0, 1, 0, -s, 0, c);
-
- c = Mathf.Cos(eulerYXZ.z);
- s = Mathf.Sin(eulerYXZ.z);
- var zmat = new Basis(c, -s, 0, s, c, 0, 0, 0, 1);
-
- this = ymat * xmat * zmat;
- }
-
- /// <summary>
/// Constructs a pure rotation basis matrix, rotated around the given <paramref name="axis"/>
/// by <paramref name="angle"/> (in radians). The axis must be a normalized vector.
/// </summary>
@@ -800,6 +994,46 @@ namespace Godot
}
/// <summary>
+ /// Constructs a Basis matrix from Euler angles in the specified rotation order. By default, use YXZ order (most common).
+ /// </summary>
+ /// <param name="euler">The Euler angles to use.</param>
+ /// <param name="order">The order to compose the Euler angles.</param>
+ public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.YXZ)
+ {
+ real_t c, s;
+
+ c = Mathf.Cos(euler.x);
+ s = Mathf.Sin(euler.x);
+ Basis xmat = new Basis(new Vector3(1, 0, 0), new Vector3(0, c, s), new Vector3(0, -s, c));
+
+ c = Mathf.Cos(euler.y);
+ s = Mathf.Sin(euler.y);
+ Basis ymat = new Basis(new Vector3(c, 0, -s), new Vector3(0, 1, 0), new Vector3(s, 0, c));
+
+ c = Mathf.Cos(euler.z);
+ s = Mathf.Sin(euler.z);
+ Basis zmat = new Basis(new Vector3(c, s, 0), new Vector3(-s, c, 0), new Vector3(0, 0, 1));
+
+ switch (order)
+ {
+ case EulerOrder.XYZ:
+ return xmat * ymat * zmat;
+ case EulerOrder.XZY:
+ return xmat * zmat * ymat;
+ case EulerOrder.YXZ:
+ return ymat * xmat * zmat;
+ case EulerOrder.YZX:
+ return ymat * zmat * xmat;
+ case EulerOrder.ZXY:
+ return zmat * xmat * ymat;
+ case EulerOrder.ZYX:
+ return zmat * ymat * xmat;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(order));
+ }
+ }
+
+ /// <summary>
/// Constructs a pure scale basis matrix with no rotation or shearing.
/// The scale values are set as the main diagonal of the matrix,
/// and all of the other parts of the matrix are zero.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
index 1b7f5158fd..bdedd2e87a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
@@ -72,7 +72,7 @@ namespace Godot
/// <param name="delegate">Delegate method that will be called.</param>
public Callable(Delegate @delegate)
{
- _target = null;
+ _target = @delegate?.Target as Object;
_method = null;
_delegate = @delegate;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index 3483a04c83..ee9e59f9fa 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -597,7 +597,7 @@ namespace Godot
/// <exception name="ArgumentOutOfRangeException">
/// <paramref name="rgba"/> color code is invalid.
/// </exception>
- private static Color FromHTML(string rgba)
+ private static Color FromHTML(ReadOnlySpan<char> rgba)
{
Color c;
if (rgba.Length == 0)
@@ -611,7 +611,7 @@ namespace Godot
if (rgba[0] == '#')
{
- rgba = rgba.Substring(1);
+ rgba = rgba.Slice(1);
}
// If enabled, use 1 hex digit per channel instead of 2.
@@ -665,22 +665,22 @@ namespace Godot
if (c.r < 0)
{
- throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
+ throw new ArgumentOutOfRangeException($"Invalid color code. Red part is not valid hexadecimal: {rgba}");
}
if (c.g < 0)
{
- throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
+ throw new ArgumentOutOfRangeException($"Invalid color code. Green part is not valid hexadecimal: {rgba}");
}
if (c.b < 0)
{
- throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
+ throw new ArgumentOutOfRangeException($"Invalid color code. Blue part is not valid hexadecimal: {rgba}");
}
if (c.a < 0)
{
- throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
+ throw new ArgumentOutOfRangeException($"Invalid color code. Alpha part is not valid hexadecimal: {rgba}");
}
return c;
}
@@ -817,9 +817,9 @@ namespace Godot
value = max;
}
- private static int ParseCol4(string str, int ofs)
+ private static int ParseCol4(ReadOnlySpan<char> str, int index)
{
- char character = str[ofs];
+ char character = str[index];
if (character >= '0' && character <= '9')
{
@@ -836,9 +836,9 @@ namespace Godot
return -1;
}
- private static int ParseCol8(string str, int ofs)
+ private static int ParseCol8(ReadOnlySpan<char> str, int index)
{
- return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1);
+ return ParseCol4(str, index) * 16 + ParseCol4(str, index + 1);
}
private static string ToHex32(float val)
@@ -847,16 +847,16 @@ namespace Godot
return b.HexEncode();
}
- internal static bool HtmlIsValid(string color)
+ internal static bool HtmlIsValid(ReadOnlySpan<char> color)
{
- if (string.IsNullOrEmpty(color))
+ if (color.IsEmpty)
{
return false;
}
if (color[0] == '#')
{
- color = color.Substring(1);
+ color = color.Slice(1);
}
// Check if the amount of hex digits is valid.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 140fc167ba..76b186cd15 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -721,8 +721,9 @@ namespace Godot.NativeInterop
if (p_managed_callable.Delegate != null)
{
var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
+ IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero;
NativeFuncs.godotsharp_callable_new_with_delegate(
- GCHandle.ToIntPtr(gcHandle), out godot_callable callable);
+ GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable);
return callable;
}
else
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index bd00611383..20ede9f0dd 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -141,7 +141,7 @@ namespace Godot.NativeInterop
public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest,
in godot_string p_element);
- public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle,
+ public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object,
out godot_callable r_callable);
internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable,
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 276701cdaa..2717b945f6 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -447,9 +447,10 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String
r_dest->append(*p_element);
}
-void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, Callable *r_callable) {
+void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) {
// TODO: Use pooling for ManagedCallable instances.
- CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle));
+ ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID();
+ CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid));
memnew_placement(r_callable, Callable(managed_callable));
}
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index 9305dc645a..0c2c533090 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -79,7 +79,9 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
}
ObjectID ManagedCallable::get_object() const {
- // TODO: If the delegate target extends Godot.Object, use that instead!
+ if (object_id != ObjectID()) {
+ return object_id;
+ }
return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
}
@@ -104,7 +106,7 @@ void ManagedCallable::release_delegate_handle() {
// Why you do this clang-format...
/* clang-format off */
-ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle) : delegate_handle(p_delegate_handle) {
+ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) {
#ifdef GD_MONO_HOT_RELOAD
{
MutexLock lock(instances_mutex);
diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h
index aa3344f4d5..26cd164fb6 100644
--- a/modules/mono/managed_callable.h
+++ b/modules/mono/managed_callable.h
@@ -40,6 +40,7 @@
class ManagedCallable : public CallableCustom {
friend class CSharpLanguage;
GCHandleIntPtr delegate_handle;
+ ObjectID object_id;
#ifdef GD_MONO_HOT_RELOAD
SelfList<ManagedCallable> self_instance = this;
@@ -66,7 +67,7 @@ public:
void release_delegate_handle();
- ManagedCallable(GCHandleIntPtr p_delegate_handle);
+ ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id);
~ManagedCallable();
};
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 2c3ebccaeb..9755f426d5 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -48,6 +48,8 @@ void MultiplayerSynchronizer::_stop() {
return;
}
#endif
+ root_node_cache = ObjectID();
+ reset();
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->object_configuration_remove(node, this);
@@ -60,8 +62,11 @@ void MultiplayerSynchronizer::_start() {
return;
}
#endif
+ root_node_cache = ObjectID();
+ reset();
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
+ root_node_cache = node->get_instance_id();
get_multiplayer()->object_configuration_add(node, this);
_update_process();
}
@@ -94,6 +99,40 @@ void MultiplayerSynchronizer::_update_process() {
}
}
+Node *MultiplayerSynchronizer::get_root_node() {
+ return root_node_cache.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(root_node_cache)) : nullptr;
+}
+
+void MultiplayerSynchronizer::reset() {
+ net_id = 0;
+ last_sync_msec = 0;
+ last_inbound_sync = 0;
+}
+
+uint32_t MultiplayerSynchronizer::get_net_id() const {
+ return net_id;
+}
+
+void MultiplayerSynchronizer::set_net_id(uint32_t p_net_id) {
+ net_id = p_net_id;
+}
+
+bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_msec) {
+ if (p_msec >= last_sync_msec + interval_msec) {
+ last_sync_msec = p_msec;
+ return true;
+ }
+ return false;
+}
+
+bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time) {
+ if (p_network_time <= last_inbound_sync && last_inbound_sync - p_network_time < 32767) {
+ return false;
+ }
+ last_inbound_sync = p_network_time;
+ return true;
+}
+
PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
@@ -263,10 +302,6 @@ double MultiplayerSynchronizer::get_replication_interval() const {
return double(interval_msec) / 1000.0;
}
-uint64_t MultiplayerSynchronizer::get_replication_interval_msec() const {
- return interval_msec;
-}
-
void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) {
replication_config = p_config;
}
@@ -299,10 +334,11 @@ NodePath MultiplayerSynchronizer::get_root_path() const {
void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
- if (!node) {
+ if (!node || get_multiplayer_authority() == p_peer_id) {
Node::set_multiplayer_authority(p_peer_id, p_recursive);
return;
}
+
get_multiplayer()->object_configuration_remove(node, this);
Node::set_multiplayer_authority(p_peer_id, p_recursive);
get_multiplayer()->object_configuration_add(node, this);
diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h
index f10a95a1d4..9a7ce717cd 100644
--- a/modules/multiplayer/multiplayer_synchronizer.h
+++ b/modules/multiplayer/multiplayer_synchronizer.h
@@ -53,6 +53,11 @@ private:
HashSet<Callable> visibility_filters;
HashSet<int> peer_visibility;
+ ObjectID root_node_cache;
+ uint64_t last_sync_msec = 0;
+ uint16_t last_inbound_sync = 0;
+ uint32_t net_id = 0;
+
static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop);
void _start();
void _stop();
@@ -66,11 +71,19 @@ public:
static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs);
static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state);
+ void reset();
+ Node *get_root_node();
+
+ uint32_t get_net_id() const;
+ void set_net_id(uint32_t p_net_id);
+
+ bool update_outbound_sync_time(uint64_t p_msec);
+ bool update_inbound_sync_time(uint16_t p_network_time);
+
PackedStringArray get_configuration_warnings() const override;
void set_replication_interval(double p_interval);
double get_replication_interval() const;
- uint64_t get_replication_interval_msec() const;
void set_replication_config(Ref<SceneReplicationConfig> p_config);
Ref<SceneReplicationConfig> get_replication_config();
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index 53d8e82dfc..df9985916b 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -30,21 +30,47 @@
#include "scene_replication_interface.h"
+#include "scene_multiplayer.h"
+
#include "core/io/marshalls.h"
#include "scene/main/node.h"
-
-#include "multiplayer_spawner.h"
-#include "multiplayer_synchronizer.h"
-#include "scene_multiplayer.h"
+#include "scene/scene_string_names.h"
#define MAKE_ROOM(m_amount) \
if (packet_cache.size() < m_amount) \
packet_cache.resize(m_amount);
-void SceneReplicationInterface::_free_remotes(int p_id) {
- const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id);
- for (const KeyValue<uint32_t, ObjectID> &E : remotes) {
- Node *node = rep_state->get_node(E.value);
+SceneReplicationInterface::TrackedNode &SceneReplicationInterface::_track(const ObjectID &p_id) {
+ if (!tracked_nodes.has(p_id)) {
+ tracked_nodes[p_id] = TrackedNode(p_id);
+ Node *node = get_id_as<Node>(p_id);
+ node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationInterface::_untrack).bind(p_id), Node::CONNECT_ONE_SHOT);
+ }
+ return tracked_nodes[p_id];
+}
+
+void SceneReplicationInterface::_untrack(const ObjectID &p_id) {
+ if (!tracked_nodes.has(p_id)) {
+ return;
+ }
+ uint32_t net_id = tracked_nodes[p_id].net_id;
+ uint32_t peer = tracked_nodes[p_id].remote_peer;
+ tracked_nodes.erase(p_id);
+ // If it was spawned by a remote, remove it from the received nodes.
+ if (peer && peers_info.has(peer)) {
+ peers_info[peer].recv_nodes.erase(net_id);
+ }
+ // If we spawned or synced it, we need to remove it from any peer it was sent to.
+ if (net_id || peer == 0) {
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.spawn_nodes.erase(p_id);
+ }
+ }
+}
+
+void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) {
+ for (const KeyValue<uint32_t, ObjectID> &E : p_info.recv_nodes) {
+ Node *node = tracked_nodes.has(E.value) ? get_id_as<Node>(E.value) : nullptr;
ERR_CONTINUE(!node);
node->queue_delete();
}
@@ -52,34 +78,48 @@ void SceneReplicationInterface::_free_remotes(int p_id) {
void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) {
if (p_connected) {
- rep_state->on_peer_change(p_id, p_connected);
- for (const ObjectID &oid : rep_state->get_spawned_nodes()) {
+ peers_info[p_id] = PeerInfo();
+ for (const ObjectID &oid : spawned_nodes) {
_update_spawn_visibility(p_id, oid);
}
- for (const ObjectID &oid : rep_state->get_synced_nodes()) {
- MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
- ERR_CONTINUE(!sync); // ERR_BUG
- if (sync->is_multiplayer_authority()) {
- _update_sync_visibility(p_id, oid);
- }
+ for (const ObjectID &oid : sync_nodes) {
+ _update_sync_visibility(p_id, get_id_as<MultiplayerSynchronizer>(oid));
}
} else {
- _free_remotes(p_id);
- rep_state->on_peer_change(p_id, p_connected);
+ ERR_FAIL_COND(!peers_info.has(p_id));
+ _free_remotes(peers_info[p_id]);
+ peers_info.erase(p_id);
}
}
void SceneReplicationInterface::on_reset() {
- for (int pid : rep_state->get_peers()) {
- _free_remotes(pid);
+ for (const KeyValue<int, PeerInfo> &E : peers_info) {
+ _free_remotes(E.value);
}
- rep_state->reset();
+ peers_info.clear();
+ // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned.
+ for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) {
+ TrackedNode &tobj = E.value;
+ tobj.net_id = 0;
+ tobj.remote_peer = 0;
+ }
+ for (const ObjectID &oid : sync_nodes) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid);
+ ERR_CONTINUE(!sync);
+ sync->reset();
+ }
+ last_net_id = 0;
}
void SceneReplicationInterface::on_network_process() {
uint64_t msec = OS::get_singleton()->get_ticks_msec();
- for (int peer : rep_state->get_peers()) {
- _send_sync(peer, msec);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ const HashSet<ObjectID> to_sync = E.value.sync_nodes;
+ if (to_sync.is_empty()) {
+ continue; // Nothing to sync
+ }
+ uint16_t sync_net_time = ++E.value.last_sent_sync;
+ _send_sync(E.key, to_sync, sync_net_time, msec);
}
}
@@ -88,14 +128,19 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER);
- Error err = rep_state->config_add_spawn(node, spawner);
- ERR_FAIL_COND_V(err != OK, err);
+ // Track node.
const ObjectID oid = node->get_instance_id();
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);
+ tobj.spawner = spawner->get_instance_id();
+ spawned_nodes.insert(oid);
+
if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) {
- rep_state->ensure_net_id(oid);
+ if (tobj.net_id == 0) {
+ tobj.net_id = ++last_net_id;
+ }
_update_spawn_visibility(0, oid);
}
- ERR_FAIL_COND_V(err != OK, err);
return OK;
}
@@ -109,14 +154,22 @@ Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
Error err = _make_despawn_packet(node, len);
ERR_FAIL_COND_V(err != OK, ERR_BUG);
const ObjectID oid = p_obj->get_instance_id();
- for (int pid : rep_state->get_peers()) {
- if (!rep_state->is_peer_spawn(pid, oid)) {
+ for (const KeyValue<int, PeerInfo> &E : peers_info) {
+ if (!E.value.spawn_nodes.has(oid)) {
continue;
}
- _send_raw(packet_cache.ptr(), len, pid, true);
+ _send_raw(packet_cache.ptr(), len, E.key, true);
}
// Also remove spawner tracking from the replication state.
- return rep_state->config_del_spawn(node, spawner);
+ ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_INVALID_PARAMETER);
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.spawner != spawner->get_instance_id(), ERR_INVALID_PARAMETER);
+ tobj.spawner = ObjectID();
+ spawned_nodes.erase(oid);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.spawn_nodes.erase(oid);
+ }
+ return OK;
}
Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) {
@@ -125,28 +178,40 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
- const ObjectID oid = node->get_instance_id();
- MultiplayerSpawner *spawner = rep_state->get_spawner(oid);
- ERR_FAIL_COND_V_MSG(spawner && spawner->get_multiplayer_authority() != sync->get_multiplayer_authority(), ERR_INVALID_PARAMETER, "The authority of the MultiplayerSynchronizer \"" + String(sync->get_path()) + "\" differs from the authority of its \"root_node\" spawner and will not sync. Change the \"root_node\" of the MultiplayerSynchronizer to be a child of the scene root instead.");
-
- // Add to synchronizer list and setup visibility.
- rep_state->config_add_sync(node, sync);
- sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed).bind(oid));
- if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) {
- _update_sync_visibility(0, oid);
- }
-
- // Try to apply initial state if spawning (hack to apply if before ready).
- if (pending_spawn == p_obj->get_instance_id()) {
- pending_spawn = ObjectID(); // Make sure this only happens once.
- const List<NodePath> props = sync->get_replication_config()->get_spawn_properties();
- Vector<Variant> vars;
- vars.resize(props.size());
- int consumed;
- Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed);
- ERR_FAIL_COND_V(err, err);
- err = MultiplayerSynchronizer::set_state(props, node, vars);
- ERR_FAIL_COND_V(err, err);
+ // Add to synchronizer list.
+ TrackedNode &tobj = _track(p_obj->get_instance_id());
+ const ObjectID sid = sync->get_instance_id();
+ tobj.synchronizers.insert(sid);
+ sync_nodes.insert(sid);
+
+ // Update visibility.
+ sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed).bind(sync->get_instance_id()));
+ _update_sync_visibility(0, sync);
+
+ if (pending_spawn == p_obj->get_instance_id() && sync->get_multiplayer_authority() == pending_spawn_remote) {
+ // Try to apply synchronizer Net ID
+ ERR_FAIL_COND_V_MSG(pending_sync_net_ids.is_empty(), ERR_INVALID_DATA, vformat("The MultiplayerSynchronizer at path \"%s\" is unable to process the pending spawn since it has no network ID. This might happen when changing the multiplayer authority during the \"_ready\" callback. Make sure to only change the authority of multiplayer synchronizers during \"_enter_tree\" or the \"_spawn_custom\" callback of their multiplayer spawner.", sync->get_path()));
+ ERR_FAIL_COND_V(!peers_info.has(pending_spawn_remote), ERR_INVALID_DATA);
+ uint32_t net_id = pending_sync_net_ids[0];
+ pending_sync_net_ids.pop_front();
+ peers_info[pending_spawn_remote].recv_sync_ids[net_id] = sync->get_instance_id();
+
+ // Try to apply spawn state (before ready).
+ if (pending_buffer_size > 0) {
+ ERR_FAIL_COND_V(!node || sync->get_replication_config().is_null(), ERR_UNCONFIGURED);
+ int consumed = 0;
+ const List<NodePath> props = sync->get_replication_config()->get_spawn_properties();
+ Vector<Variant> vars;
+ vars.resize(props.size());
+ Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed);
+ ERR_FAIL_COND_V(err, err);
+ if (consumed > 0) {
+ pending_buffer += consumed;
+ pending_buffer_size -= consumed;
+ err = MultiplayerSynchronizer::set_state(props, node, vars);
+ ERR_FAIL_COND_V(err, err);
+ }
+ }
}
return OK;
}
@@ -157,59 +222,98 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed));
- return rep_state->config_del_sync(node, sync);
+ // Untrack synchronizer.
+ const ObjectID oid = node->get_instance_id();
+ const ObjectID sid = sync->get_instance_id();
+ ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_INVALID_PARAMETER);
+ TrackedNode &tobj = _track(oid);
+ tobj.synchronizers.erase(sid);
+ sync_nodes.erase(sid);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.sync_nodes.erase(sid);
+ if (sync->get_net_id()) {
+ E.value.recv_sync_ids.erase(sync->get_net_id());
+ }
+ }
+ return OK;
}
-void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_oid) {
- if (rep_state->is_spawned_node(p_oid)) {
- _update_spawn_visibility(p_peer, p_oid);
- }
- if (rep_state->is_synced_node(p_oid)) {
- _update_sync_visibility(p_peer, p_oid);
+void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(p_sid);
+ ERR_FAIL_COND(!sync); // Bug.
+ Node *node = sync->get_root_node();
+ ERR_FAIL_COND(!node); // Bug.
+ const ObjectID oid = node->get_instance_id();
+ if (spawned_nodes.has(oid)) {
+ _update_spawn_visibility(p_peer, oid);
}
+ _update_sync_visibility(p_peer, sync);
}
-Error SceneReplicationInterface::_update_sync_visibility(int p_peer, const ObjectID &p_oid) {
- MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid);
- ERR_FAIL_COND_V(!sync || !sync->is_multiplayer_authority(), ERR_BUG);
- bool is_visible = sync->is_visible_to(p_peer);
+Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) {
+ ERR_FAIL_COND_V(!p_sync, ERR_BUG);
+ if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority()) {
+ return OK;
+ }
+
+ const ObjectID &sid = p_sync->get_instance_id();
+ bool is_visible = p_sync->is_visible_to(p_peer);
if (p_peer == 0) {
- for (int pid : rep_state->get_peers()) {
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
// Might be visible to this specific peer.
- is_visible = is_visible || sync->is_visible_to(pid);
- if (rep_state->is_peer_sync(pid, p_oid) == is_visible) {
+ is_visible = is_visible || p_sync->is_visible_to(E.key);
+ if (is_visible == E.value.sync_nodes.has(sid)) {
continue;
}
if (is_visible) {
- rep_state->peer_add_sync(pid, p_oid);
+ E.value.sync_nodes.insert(sid);
} else {
- rep_state->peer_del_sync(pid, p_oid);
+ E.value.sync_nodes.erase(sid);
}
}
return OK;
} else {
- if (is_visible == rep_state->is_peer_sync(p_peer, p_oid)) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ if (is_visible == peers_info[p_peer].sync_nodes.has(sid)) {
return OK;
}
if (is_visible) {
- return rep_state->peer_add_sync(p_peer, p_oid);
+ peers_info[p_peer].sync_nodes.insert(sid);
} else {
- return rep_state->peer_del_sync(p_peer, p_oid);
+ peers_info[p_peer].sync_nodes.erase(sid);
}
+ return OK;
}
}
Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) {
- MultiplayerSpawner *spawner = rep_state->get_spawner(p_oid);
- MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid);
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_oid));
+ const TrackedNode *tnode = tracked_nodes.getptr(p_oid);
+ ERR_FAIL_COND_V(!tnode, ERR_BUG);
+ MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner);
+ Node *node = get_id_as<Node>(p_oid);
ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG);
- bool is_visible = !sync || sync->is_visible_to(p_peer);
+ ERR_FAIL_COND_V(!tracked_nodes.has(p_oid), ERR_BUG);
+ const HashSet<ObjectID> synchronizers = tracked_nodes[p_oid].synchronizers;
+ bool is_visible = true;
+ for (const ObjectID &sid : synchronizers) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid);
+ ERR_CONTINUE(!sync);
+ if (!sync->is_multiplayer_authority()) {
+ continue;
+ }
+ // Spawn visibility is composed using OR when multiple synchronizers are present.
+ if (sync->is_visible_to(p_peer)) {
+ is_visible = true;
+ break;
+ }
+ is_visible = false;
+ }
// Spawn (and despawn) when needed.
HashSet<int> to_spawn;
HashSet<int> to_despawn;
if (p_peer) {
- if (is_visible == rep_state->is_peer_spawn(p_peer, p_oid)) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ if (is_visible == peers_info[p_peer].spawn_nodes.has(p_oid)) {
return OK;
}
if (is_visible) {
@@ -219,33 +323,37 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
}
} else {
// Check visibility for each peers.
- for (int pid : rep_state->get_peers()) {
- bool peer_visible = is_visible || sync->is_visible_to(pid);
- if (peer_visible == rep_state->is_peer_spawn(pid, p_oid)) {
- continue;
- }
- if (peer_visible) {
- to_spawn.insert(pid);
+ for (const KeyValue<int, PeerInfo> &E : peers_info) {
+ if (is_visible) {
+ // This is fast, since the the object is visibile to everyone, we don't need to check each peer.
+ if (E.value.spawn_nodes.has(p_oid)) {
+ // Already spawned.
+ continue;
+ }
+ to_spawn.insert(E.key);
} else {
- to_despawn.insert(pid);
+ // Need to check visibility for each peer.
+ _update_spawn_visibility(E.key, p_oid);
}
}
}
if (to_spawn.size()) {
int len = 0;
- _make_spawn_packet(node, len);
+ _make_spawn_packet(node, spawner, len);
for (int pid : to_spawn) {
+ ERR_CONTINUE(!peers_info.has(pid));
int path_id;
multiplayer->get_path_cache()->send_object_cache(spawner, pid, path_id);
_send_raw(packet_cache.ptr(), len, pid, true);
- rep_state->peer_add_spawn(pid, p_oid);
+ peers_info[pid].spawn_nodes.insert(p_oid);
}
}
if (to_despawn.size()) {
int len = 0;
_make_despawn_packet(node, len);
for (int pid : to_despawn) {
- rep_state->peer_del_spawn(pid, p_oid);
+ ERR_CONTINUE(!peers_info.has(pid));
+ peers_info[pid].spawn_nodes.erase(p_oid);
_send_raw(packet_cache.ptr(), len, pid, true);
}
}
@@ -268,20 +376,20 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
return peer->put_packet(p_buffer, p_size);
}
-Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) {
- ERR_FAIL_COND_V(!multiplayer, ERR_BUG);
+Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len) {
+ ERR_FAIL_COND_V(!multiplayer || !p_node || !p_spawner, ERR_BUG);
const ObjectID oid = p_node->get_instance_id();
- MultiplayerSpawner *spawner = rep_state->get_spawner(oid);
- ERR_FAIL_COND_V(!spawner || !p_node, ERR_BUG);
+ const TrackedNode *tnode = tracked_nodes.getptr(oid);
+ ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER);
- uint32_t nid = rep_state->get_net_id(oid);
+ uint32_t nid = tnode->net_id;
ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED);
// Prepare custom arg and scene_id
- uint8_t scene_id = spawner->find_spawnable_scene_index_from_object(oid);
+ uint8_t scene_id = p_spawner->find_spawnable_scene_index_from_object(oid);
bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID;
- Variant spawn_arg = spawner->get_spawn_argument(oid);
+ Variant spawn_arg = p_spawner->get_spawn_argument(oid);
int spawn_arg_size = 0;
if (is_custom) {
Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false);
@@ -289,31 +397,51 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) {
}
// Prepare spawn state.
+ List<NodePath> state_props;
+ List<uint32_t> sync_ids;
+ const HashSet<ObjectID> synchronizers = tnode->synchronizers;
+ for (const ObjectID &sid : synchronizers) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid);
+ if (!sync->is_multiplayer_authority()) {
+ continue;
+ }
+ ERR_CONTINUE(!sync);
+ ERR_FAIL_COND_V(sync->get_replication_config().is_null(), ERR_BUG);
+ for (const NodePath &prop : sync->get_replication_config()->get_spawn_properties()) {
+ state_props.push_back(prop);
+ }
+ // Ensure the synchronizer has an ID.
+ if (sync->get_net_id() == 0) {
+ sync->set_net_id(++last_net_id);
+ }
+ sync_ids.push_back(sync->get_net_id());
+ }
int state_size = 0;
Vector<Variant> state_vars;
Vector<const Variant *> state_varp;
- MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid);
- if (synchronizer) {
- ERR_FAIL_COND_V(synchronizer->get_replication_config().is_null(), ERR_BUG);
- const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties();
- Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp);
+ if (state_props.size()) {
+ Error err = MultiplayerSynchronizer::get_state(state_props, p_node, state_vars, state_varp);
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state.");
err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), nullptr, state_size);
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state.");
}
// Encode scene ID, path ID, net ID, node name.
- int path_id = multiplayer->get_path_cache()->make_object_cache(spawner);
+ int path_id = multiplayer->get_path_cache()->make_object_cache(p_spawner);
CharString cname = p_node->get_name().operator String().utf8();
int nlen = encode_cstring(cname.get_data(), nullptr);
- MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
+ MAKE_ROOM(1 + 1 + 4 + 4 + 4 + 4 * sync_ids.size() + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_SPAWN;
ptr[1] = scene_id;
int ofs = 2;
ofs += encode_uint32(path_id, &ptr[ofs]);
ofs += encode_uint32(nid, &ptr[ofs]);
+ ofs += encode_uint32(sync_ids.size(), &ptr[ofs]);
ofs += encode_uint32(nlen, &ptr[ofs]);
+ for (uint32_t snid : sync_ids) {
+ ofs += encode_uint32(snid, &ptr[ofs]);
+ }
ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
// Write args
if (is_custom) {
@@ -334,18 +462,20 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) {
Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) {
const ObjectID oid = p_node->get_instance_id();
+ const TrackedNode *tnode = tracked_nodes.getptr(oid);
+ ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER);
MAKE_ROOM(5);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN;
int ofs = 1;
- uint32_t nid = rep_state->get_net_id(oid);
+ uint32_t nid = tnode->net_id;
ofs += encode_uint32(nid, &ptr[ofs]);
r_len = ofs;
return OK;
}
Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
- ERR_FAIL_COND_V_MSG(p_buffer_len < 14, ERR_INVALID_DATA, "Invalid spawn packet received");
+ ERR_FAIL_COND_V_MSG(p_buffer_len < 18, ERR_INVALID_DATA, "Invalid spawn packet received");
int ofs = 1; // The spawn/despawn command.
uint8_t scene_id = p_buffer[ofs];
ofs += 1;
@@ -357,9 +487,16 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
ofs += 4;
+ uint32_t sync_len = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
uint32_t name_len = decode_uint32(&p_buffer[ofs]);
ofs += 4;
- ERR_FAIL_COND_V_MSG(name_len > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len));
+ ERR_FAIL_COND_V_MSG(name_len + (sync_len * 4) > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len + (sync_len * 4)));
+ List<uint32_t> sync_ids;
+ for (uint32_t i = 0; i < sync_len; i++) {
+ sync_ids.push_back(decode_uint32(&p_buffer[ofs]));
+ ofs += 4;
+ }
ERR_FAIL_COND_V_MSG(name_len < 1, ERR_INVALID_DATA, "Zero spawn name size.");
// We need to make sure no trickery happens here, but we want to allow autogenerated ("@") node names.
@@ -390,20 +527,35 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
}
ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED);
node->set_name(name);
- rep_state->peer_add_remote(p_from, net_id, node, spawner);
+
+ // Add and track remote
+ ERR_FAIL_COND_V(!peers_info.has(p_from), ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(peers_info[p_from].recv_nodes.has(net_id), ERR_ALREADY_IN_USE);
+ ObjectID oid = node->get_instance_id();
+ TrackedNode &tobj = _track(oid);
+ tobj.spawner = spawner->get_instance_id();
+ tobj.net_id = net_id;
+ tobj.remote_peer = p_from;
+ peers_info[p_from].recv_nodes[net_id] = oid;
+
// The initial state will be applied during the sync config (i.e. before _ready).
- int state_len = p_buffer_len - ofs;
- if (state_len) {
- pending_spawn = node->get_instance_id();
- pending_buffer = &p_buffer[ofs];
- pending_buffer_size = state_len;
- }
+ pending_spawn = node->get_instance_id();
+ pending_spawn_remote = p_from;
+ pending_buffer_size = p_buffer_len - ofs;
+ pending_buffer = pending_buffer_size > 0 ? &p_buffer[ofs] : nullptr;
+ pending_sync_net_ids = sync_ids;
+
parent->add_child(node);
spawner->emit_signal(SNAME("spawned"), node);
pending_spawn = ObjectID();
+ pending_spawn_remote = 0;
pending_buffer = nullptr;
pending_buffer_size = 0;
+ if (pending_sync_net_ids.size()) {
+ pending_sync_net_ids.clear();
+ ERR_FAIL_V(ERR_INVALID_DATA); // Should have been consumed.
+ }
return OK;
}
@@ -412,12 +564,18 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
int ofs = 1; // The spawn/despawn command.
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
ofs += 4;
- Node *node = nullptr;
- Error err = rep_state->peer_del_remote(p_from, net_id, &node);
- ERR_FAIL_COND_V(err != OK, err);
+
+ // Untrack remote
+ ERR_FAIL_COND_V(!peers_info.has(p_from), ERR_UNAUTHORIZED);
+ PeerInfo &pinfo = peers_info[p_from];
+ ERR_FAIL_COND_V(!pinfo.recv_nodes.has(net_id), ERR_UNAUTHORIZED);
+ Node *node = get_id_as<Node>(pinfo.recv_nodes[net_id]);
ERR_FAIL_COND_V(!node, ERR_BUG);
+ pinfo.recv_nodes.erase(net_id);
- MultiplayerSpawner *spawner = rep_state->get_spawner(node->get_instance_id());
+ const ObjectID oid = node->get_instance_id();
+ ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_BUG);
+ MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tracked_nodes[oid].spawner);
ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
@@ -430,27 +588,24 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
return OK;
}
-void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
- const HashSet<ObjectID> &to_sync = rep_state->get_peer_sync_nodes(p_peer);
- if (to_sync.is_empty()) {
- return;
- }
+void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec) {
MAKE_ROOM(sync_mtu);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC;
int ofs = 1;
- ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]);
+ ofs += encode_uint16(p_sync_net_time, &ptr[1]);
// Can only send updates for already notified nodes.
// This is a lazy implementation, we could optimize much more here with by grouping by replication config.
- for (const ObjectID &oid : to_sync) {
- if (!rep_state->update_sync_time(oid, p_msec)) {
+ for (const ObjectID &oid : p_synchronizers) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid);
+ ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority());
+ if (!sync->update_outbound_sync_time(p_msec)) {
continue; // nothing to sync.
}
- MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
- ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid());
- Node *node = rep_state->get_node(oid);
+
+ Node *node = sync->get_root_node();
ERR_CONTINUE(!node);
- uint32_t net_id = rep_state->get_net_id(oid);
+ uint32_t net_id = sync->get_net_id();
if (net_id == 0 || (net_id & 0x80000000)) {
int path_id = 0;
bool verified = multiplayer->get_path_cache()->send_object_cache(sync, p_peer, path_id);
@@ -458,7 +613,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
if (net_id == 0) {
// First time path based ID.
net_id = path_id | 0x80000000;
- rep_state->set_net_id(oid, net_id | 0x80000000);
+ sync->set_net_id(net_id | 0x80000000);
}
if (!verified) {
// The path based sync is not yet confirmed, skipping.
@@ -481,7 +636,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
ofs = 3;
}
if (size) {
- ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]);
+ ofs += encode_uint32(sync->get_net_id(), &ptr[ofs]);
ofs += encode_uint32(size, &ptr[ofs]);
MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size);
ofs += size;
@@ -497,33 +652,32 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu
ERR_FAIL_COND_V_MSG(p_buffer_len < 11, ERR_INVALID_DATA, "Invalid sync packet received");
uint16_t time = decode_uint16(&p_buffer[1]);
int ofs = 3;
- rep_state->peer_sync_recv(p_from, time);
while (ofs + 8 < p_buffer_len) {
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
ofs += 4;
uint32_t size = decode_uint32(&p_buffer[ofs]);
ofs += 4;
- Node *node = nullptr;
+ MultiplayerSynchronizer *sync = nullptr;
if (net_id & 0x80000000) {
- MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_from, net_id & 0x7FFFFFFF));
- ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED);
- node = sync->get_node(sync->get_root_path());
- } else {
- node = rep_state->peer_get_remote(p_from, net_id);
+ sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_from, net_id & 0x7FFFFFFF));
+ } else if (peers_info[p_from].recv_sync_ids.has(net_id)) {
+ const ObjectID &sid = peers_info[p_from].recv_sync_ids[net_id];
+ sync = get_id_as<MultiplayerSynchronizer>(sid);
}
- if (!node) {
+ if (!sync) {
// Not received yet.
ofs += size;
continue;
}
- const ObjectID oid = node->get_instance_id();
- if (!rep_state->update_last_node_sync(oid, time)) {
+ Node *node = sync->get_root_node();
+ if (sync->get_multiplayer_authority() != p_from || !node) {
+ ERR_CONTINUE(true);
+ }
+ if (!sync->update_inbound_sync_time(time)) {
// State is too old.
ofs += size;
continue;
}
- MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
- ERR_FAIL_COND_V(!sync, ERR_BUG);
ERR_FAIL_COND_V(size > uint32_t(p_buffer_len - ofs), ERR_BUG);
const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
Vector<Variant> vars;
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index 8981647429..ee454f604e 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -31,9 +31,10 @@
#ifndef SCENE_REPLICATION_INTERFACE_H
#define SCENE_REPLICATION_INTERFACE_H
-#include "scene/main/multiplayer_api.h"
+#include "core/object/ref_counted.h"
-#include "scene_replication_state.h"
+#include "multiplayer_spawner.h"
+#include "multiplayer_synchronizer.h"
class SceneMultiplayer;
@@ -41,25 +42,68 @@ class SceneReplicationInterface : public RefCounted {
GDCLASS(SceneReplicationInterface, RefCounted);
private:
- void _send_sync(int p_peer, uint64_t p_msec);
- Error _make_spawn_packet(Node *p_node, int &r_len);
- Error _make_despawn_packet(Node *p_node, int &r_len);
- Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
+ struct TrackedNode {
+ ObjectID id;
+ uint32_t net_id = 0;
+ uint32_t remote_peer = 0;
+ ObjectID spawner;
+ HashSet<ObjectID> synchronizers;
- void _visibility_changed(int p_peer, ObjectID p_oid);
- Error _update_sync_visibility(int p_peer, const ObjectID &p_oid);
- Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid);
- void _free_remotes(int p_peer);
+ bool operator==(const ObjectID &p_other) { return id == p_other; }
- Ref<SceneReplicationState> rep_state;
- SceneMultiplayer *multiplayer = nullptr;
- PackedByteArray packet_cache;
- int sync_mtu = 1350; // Highly dependent on underlying protocol.
+ _FORCE_INLINE_ MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; }
+ TrackedNode() {}
+ TrackedNode(const ObjectID &p_id) { id = p_id; }
+ TrackedNode(const ObjectID &p_id, uint32_t p_net_id) {
+ id = p_id;
+ net_id = p_net_id;
+ }
+ };
+
+ struct PeerInfo {
+ HashSet<ObjectID> sync_nodes;
+ HashSet<ObjectID> spawn_nodes;
+ HashMap<uint32_t, ObjectID> recv_sync_ids;
+ HashMap<uint32_t, ObjectID> recv_nodes;
+ uint16_t last_sent_sync = 0;
+ };
+
+ // Replication state.
+ HashMap<int, PeerInfo> peers_info;
+ uint32_t last_net_id = 0;
+ HashMap<ObjectID, TrackedNode> tracked_nodes;
+ HashSet<ObjectID> spawned_nodes;
+ HashSet<ObjectID> sync_nodes;
- // An hack to apply the initial state before ready.
+ // Pending spawn informations.
ObjectID pending_spawn;
+ int pending_spawn_remote = 0;
const uint8_t *pending_buffer = nullptr;
int pending_buffer_size = 0;
+ List<uint32_t> pending_sync_net_ids;
+
+ // Replicator config.
+ SceneMultiplayer *multiplayer = nullptr;
+ PackedByteArray packet_cache;
+ int sync_mtu = 1350; // Highly dependent on underlying protocol.
+
+ TrackedNode &_track(const ObjectID &p_id);
+ void _untrack(const ObjectID &p_id);
+
+ void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec);
+ Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len);
+ Error _make_despawn_packet(Node *p_node, int &r_len);
+ Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
+
+ void _visibility_changed(int p_peer, ObjectID p_oid);
+ Error _update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync);
+ Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid);
+ void _free_remotes(const PeerInfo &p_info);
+
+ template <class T>
+ static T *get_id_as(const ObjectID &p_id) {
+ return p_id.is_valid() ? Object::cast_to<T>(ObjectDB::get_instance(p_id)) : nullptr;
+ }
public:
static void make_default();
@@ -78,7 +122,6 @@ public:
Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
SceneReplicationInterface(SceneMultiplayer *p_multiplayer) {
- rep_state.instantiate();
multiplayer = p_multiplayer;
}
};
diff --git a/modules/multiplayer/scene_replication_state.cpp b/modules/multiplayer/scene_replication_state.cpp
deleted file mode 100644
index fbcf0acadb..0000000000
--- a/modules/multiplayer/scene_replication_state.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-/*************************************************************************/
-/* scene_replication_state.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 "scene_replication_state.h"
-
-#include "scene/scene_string_names.h"
-
-#include "multiplayer_spawner.h"
-#include "multiplayer_synchronizer.h"
-
-SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) {
- if (!tracked_nodes.has(p_id)) {
- tracked_nodes[p_id] = TrackedNode(p_id);
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
- node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack).bind(p_id), Node::CONNECT_ONE_SHOT);
- }
- return tracked_nodes[p_id];
-}
-
-void SceneReplicationState::_untrack(const ObjectID &p_id) {
- if (tracked_nodes.has(p_id)) {
- uint32_t net_id = tracked_nodes[p_id].net_id;
- uint32_t peer = tracked_nodes[p_id].remote_peer;
- tracked_nodes.erase(p_id);
- // If it was spawned by a remote, remove it from the received nodes.
- if (peer && peers_info.has(peer)) {
- peers_info[peer].recv_nodes.erase(net_id);
- }
- // If we spawned or synced it, we need to remove it from any peer it was sent to.
- if (net_id || peer == 0) {
- for (KeyValue<int, PeerInfo> &E : peers_info) {
- E.value.sync_nodes.erase(p_id);
- E.value.spawn_nodes.erase(p_id);
- }
- }
- }
-}
-
-const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const {
- return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>();
-}
-
-bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) {
- TrackedNode *tnode = tracked_nodes.getptr(p_id);
- ERR_FAIL_COND_V(!tnode, false);
- if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) {
- return false;
- }
- tnode->last_sync = p_time;
- return true;
-}
-
-bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) {
- TrackedNode *tnode = tracked_nodes.getptr(p_id);
- ERR_FAIL_COND_V(!tnode, false);
- MultiplayerSynchronizer *sync = get_synchronizer(p_id);
- if (!sync) {
- return false;
- }
- if (tnode->last_sync_msec == p_msec) {
- return true;
- }
- if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) {
- tnode->last_sync_msec = p_msec;
- return true;
- }
- return false;
-}
-
-uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const {
- const TrackedNode *tnode = tracked_nodes.getptr(p_id);
- ERR_FAIL_COND_V(!tnode, 0);
- return tnode->net_id;
-}
-
-void SceneReplicationState::set_net_id(const ObjectID &p_id, uint32_t p_net_id) {
- TrackedNode *tnode = tracked_nodes.getptr(p_id);
- ERR_FAIL_COND(!tnode);
- tnode->net_id = p_net_id;
-}
-
-uint32_t SceneReplicationState::ensure_net_id(const ObjectID &p_id) {
- TrackedNode *tnode = tracked_nodes.getptr(p_id);
- ERR_FAIL_COND_V(!tnode, 0);
- if (tnode->net_id == 0) {
- tnode->net_id = ++last_net_id;
- }
- return tnode->net_id;
-}
-
-void SceneReplicationState::on_peer_change(int p_peer, bool p_connected) {
- if (p_connected) {
- peers_info[p_peer] = PeerInfo();
- known_peers.insert(p_peer);
- } else {
- peers_info.erase(p_peer);
- known_peers.erase(p_peer);
- }
-}
-
-void SceneReplicationState::reset() {
- peers_info.clear();
- known_peers.clear();
- // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned.
- for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) {
- TrackedNode &tobj = E.value;
- tobj.net_id = 0;
- tobj.remote_peer = 0;
- tobj.last_sync = 0;
- }
-}
-
-Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
- const ObjectID oid = p_node->get_instance_id();
- TrackedNode &tobj = _track(oid);
- ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);
- tobj.spawner = p_spawner->get_instance_id();
- spawned_nodes.insert(oid);
- return OK;
-}
-
-Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
- const ObjectID oid = p_node->get_instance_id();
- ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
- TrackedNode &tobj = _track(oid);
- ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER);
- tobj.spawner = ObjectID();
- spawned_nodes.erase(oid);
- for (KeyValue<int, PeerInfo> &E : peers_info) {
- E.value.spawn_nodes.erase(oid);
- }
- return OK;
-}
-
-Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
- const ObjectID oid = p_node->get_instance_id();
- TrackedNode &tobj = _track(oid);
- ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE);
- tobj.synchronizer = p_sync->get_instance_id();
- synced_nodes.insert(oid);
- return OK;
-}
-
-Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
- const ObjectID oid = p_node->get_instance_id();
- ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
- TrackedNode &tobj = _track(oid);
- ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER);
- tobj.synchronizer = ObjectID();
- synced_nodes.erase(oid);
- for (KeyValue<int, PeerInfo> &E : peers_info) {
- E.value.sync_nodes.erase(oid);
- }
- return OK;
-}
-
-Error SceneReplicationState::peer_add_sync(int p_peer, const ObjectID &p_id) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
- peers_info[p_peer].sync_nodes.insert(p_id);
- return OK;
-}
-
-Error SceneReplicationState::peer_del_sync(int p_peer, const ObjectID &p_id) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
- peers_info[p_peer].sync_nodes.erase(p_id);
- return OK;
-}
-
-const HashSet<ObjectID> SceneReplicationState::get_peer_sync_nodes(int p_peer) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>());
- return peers_info[p_peer].sync_nodes;
-}
-
-bool SceneReplicationState::is_peer_sync(int p_peer, const ObjectID &p_id) const {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), false);
- return peers_info[p_peer].sync_nodes.has(p_id);
-}
-
-Error SceneReplicationState::peer_add_spawn(int p_peer, const ObjectID &p_id) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
- peers_info[p_peer].spawn_nodes.insert(p_id);
- return OK;
-}
-
-Error SceneReplicationState::peer_del_spawn(int p_peer, const ObjectID &p_id) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
- peers_info[p_peer].spawn_nodes.erase(p_id);
- return OK;
-}
-
-const HashSet<ObjectID> SceneReplicationState::get_peer_spawn_nodes(int p_peer) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>());
- return peers_info[p_peer].spawn_nodes;
-}
-
-bool SceneReplicationState::is_peer_spawn(int p_peer, const ObjectID &p_id) const {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), false);
- return peers_info[p_peer].spawn_nodes.has(p_id);
-}
-
-Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) {
- PeerInfo *info = peers_info.getptr(p_peer);
- return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr;
-}
-
-Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) {
- ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE);
- PeerInfo &pinfo = peers_info[p_peer];
- ObjectID oid = p_node->get_instance_id();
- TrackedNode &tobj = _track(oid);
- tobj.spawner = p_spawner->get_instance_id();
- tobj.net_id = p_net_id;
- tobj.remote_peer = p_peer;
- tobj.last_sync = pinfo.last_recv_sync;
- // Also track as a remote.
- ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE);
- pinfo.recv_nodes[p_net_id] = oid;
- return OK;
-}
-
-Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED);
- PeerInfo &info = peers_info[p_peer];
- ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED);
- *r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id]));
- info.recv_nodes.erase(p_net_id);
- return OK;
-}
-
-uint16_t SceneReplicationState::peer_sync_next(int p_peer) {
- ERR_FAIL_COND_V(!peers_info.has(p_peer), 0);
- PeerInfo &info = peers_info[p_peer];
- return ++info.last_sent_sync;
-}
-
-void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) {
- ERR_FAIL_COND(!peers_info.has(p_peer));
- peers_info[p_peer].last_recv_sync = p_time;
-}
diff --git a/modules/multiplayer/scene_replication_state.h b/modules/multiplayer/scene_replication_state.h
deleted file mode 100644
index bdff6ae3b7..0000000000
--- a/modules/multiplayer/scene_replication_state.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*************************************************************************/
-/* scene_replication_state.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 SCENE_REPLICATION_STATE_H
-#define SCENE_REPLICATION_STATE_H
-
-#include "core/object/ref_counted.h"
-
-#include "multiplayer_spawner.h"
-#include "multiplayer_synchronizer.h"
-
-class MultiplayerSpawner;
-class MultiplayerSynchronizer;
-class Node;
-
-class SceneReplicationState : public RefCounted {
-private:
- struct TrackedNode {
- ObjectID id;
- uint32_t net_id = 0;
- uint32_t remote_peer = 0;
- ObjectID spawner;
- ObjectID synchronizer;
- uint16_t last_sync = 0;
- uint64_t last_sync_msec = 0;
-
- bool operator==(const ObjectID &p_other) { return id == p_other; }
-
- Node *get_node() const { return id.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(id)) : nullptr; }
- MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; }
- MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; }
- TrackedNode() {}
- TrackedNode(const ObjectID &p_id) { id = p_id; }
- TrackedNode(const ObjectID &p_id, uint32_t p_net_id) {
- id = p_id;
- net_id = p_net_id;
- }
- };
-
- struct PeerInfo {
- HashSet<ObjectID> sync_nodes;
- HashSet<ObjectID> spawn_nodes;
- HashMap<uint32_t, ObjectID> recv_nodes;
- uint16_t last_sent_sync = 0;
- uint16_t last_recv_sync = 0;
- };
-
- HashSet<int> known_peers;
- uint32_t last_net_id = 0;
- HashMap<ObjectID, TrackedNode> tracked_nodes;
- HashMap<int, PeerInfo> peers_info;
- HashSet<ObjectID> spawned_nodes;
- HashSet<ObjectID> synced_nodes;
-
- TrackedNode &_track(const ObjectID &p_id);
- void _untrack(const ObjectID &p_id);
- bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); }
-
-public:
- const HashSet<int> get_peers() const { return known_peers; }
- const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; }
- bool is_spawned_node(const ObjectID &p_id) const { return spawned_nodes.has(p_id); }
- const HashSet<ObjectID> &get_synced_nodes() const { return synced_nodes; }
- bool is_synced_node(const ObjectID &p_id) const { return synced_nodes.has(p_id); }
-
- MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; }
- MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; }
- Node *get_node(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_node() : nullptr; }
- bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time);
- bool update_sync_time(const ObjectID &p_id, uint64_t p_msec);
-
- uint32_t get_net_id(const ObjectID &p_id) const;
- void set_net_id(const ObjectID &p_id, uint32_t p_net_id);
- uint32_t ensure_net_id(const ObjectID &p_id);
-
- void reset();
- void on_peer_change(int p_peer, bool p_connected);
-
- Error config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
- Error config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
-
- Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
- Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
-
- Error peer_add_sync(int p_peer, const ObjectID &p_id);
- Error peer_del_sync(int p_peer, const ObjectID &p_id);
-
- const HashSet<ObjectID> get_peer_sync_nodes(int p_peer);
- bool is_peer_sync(int p_peer, const ObjectID &p_id) const;
-
- Error peer_add_spawn(int p_peer, const ObjectID &p_id);
- Error peer_del_spawn(int p_peer, const ObjectID &p_id);
-
- const HashSet<ObjectID> get_peer_spawn_nodes(int p_peer);
- bool is_peer_spawn(int p_peer, const ObjectID &p_id) const;
-
- const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const;
- Node *peer_get_remote(int p_peer, uint32_t p_net_id);
- Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner);
- Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node);
-
- uint16_t peer_sync_next(int p_peer);
- void peer_sync_recv(int p_peer, uint16_t p_time);
-
- SceneReplicationState() {}
-};
-
-#endif // SCENE_REPLICATION_STATE_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 394c32f20d..83862e1e34 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -238,6 +238,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
to_visit.clear();
to_visit.push_back(0);
least_cost_id = 0;
+ prev_least_cost_id = -1;
reachable_end = nullptr;
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 8d279f9dd3..101a66371b 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -197,9 +197,6 @@ void NoiseTexture2D::_update_texture() {
use_thread = false;
first_time = false;
}
-#ifdef NO_THREADS
- use_thread = false;
-#endif
if (use_thread) {
if (!noise_thread.is_started()) {
noise_thread.start(_thread_function, this);
diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h
index a37a789cbe..0292de68ad 100644
--- a/modules/websocket/remote_debugger_peer_websocket.h
+++ b/modules/websocket/remote_debugger_peer_websocket.h
@@ -51,6 +51,7 @@ public:
static RemoteDebuggerPeer *create(const String &p_uri);
Error connect_to_host(const String &p_uri);
+
bool is_peer_connected() override;
int get_max_message_size() const override;
bool has_message() override;
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 66dea6cf1b..81fc941608 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -3142,6 +3142,11 @@ void DisplayServerX11::_window_changed(XEvent *event) {
return;
}
+ // Query display server about a possible new window state.
+ wd.fullscreen = _window_fullscreen_check(window_id);
+ wd.minimized = _window_minimize_check(window_id);
+ wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE");
+
{
//the position in xconfigure is not useful here, obtain it manually
int x, y;
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index 0e37afc2cc..c4b27c782d 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -184,51 +184,6 @@ Error AudioDriverWeb::capture_stop() {
return OK;
}
-#ifdef NO_THREADS
-/// ScriptProcessorNode implementation
-AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr;
-
-void AudioDriverScriptProcessor::_process_callback() {
- AudioDriverScriptProcessor::singleton->_audio_driver_capture();
- AudioDriverScriptProcessor::singleton->_audio_driver_process();
-}
-
-Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) {
- if (!godot_audio_has_script_processor()) {
- return ERR_UNAVAILABLE;
- }
- return (Error)godot_audio_script_create(&p_buffer_samples, p_channels);
-}
-
-void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
- godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
-}
-
-/// AudioWorkletNode implementation (no threads)
-AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr;
-
-Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) {
- if (!godot_audio_has_worklet()) {
- return ERR_UNAVAILABLE;
- }
- return (Error)godot_audio_worklet_create(p_channels);
-}
-
-void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
- _audio_driver_process();
- godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback);
-}
-
-void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) {
- AudioDriverWorklet *driver = AudioDriverWorklet::singleton;
- driver->_audio_driver_process(p_pos, p_samples);
-}
-
-void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) {
- AudioDriverWorklet *driver = AudioDriverWorklet::singleton;
- driver->_audio_driver_capture(p_pos, p_samples);
-}
-#else
/// AudioWorkletNode implementation (threads)
void AudioDriverWorklet::_audio_thread_func(void *p_data) {
AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data);
@@ -290,4 +245,3 @@ void AudioDriverWorklet::finish_driver() {
quit = true; // Ask thread to quit.
thread.wait_to_finish();
}
-#endif
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index dfce277c0c..0a322d61b4 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -89,46 +89,6 @@ public:
AudioDriverWeb() {}
};
-#ifdef NO_THREADS
-class AudioDriverScriptProcessor : public AudioDriverWeb {
-private:
- static void _process_callback();
-
- static AudioDriverScriptProcessor *singleton;
-
-protected:
- Error create(int &p_buffer_samples, int p_channels) override;
- void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
-
-public:
- virtual const char *get_name() const override { return "ScriptProcessor"; }
-
- virtual void lock() override {}
- virtual void unlock() override {}
-
- AudioDriverScriptProcessor() { singleton = this; }
-};
-
-class AudioDriverWorklet : public AudioDriverWeb {
-private:
- static void _process_callback(int p_pos, int p_samples);
- static void _capture_callback(int p_pos, int p_samples);
-
- static AudioDriverWorklet *singleton;
-
-protected:
- virtual Error create(int &p_buffer_size, int p_output_channels) override;
- virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
-
-public:
- virtual const char *get_name() const override { return "AudioWorklet"; }
-
- virtual void lock() override {}
- virtual void unlock() override {}
-
- AudioDriverWorklet() { singleton = this; }
-};
-#else
class AudioDriverWorklet : public AudioDriverWeb {
private:
enum {
@@ -156,6 +116,5 @@ public:
void lock() override;
void unlock() override;
};
-#endif
#endif // AUDIO_DRIVER_WEB_H
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index ebe56924df..c263ee094b 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -239,9 +239,6 @@ OS_Web::OS_Web() {
godot_js_pwa_cb(&OS_Web::update_pwa_state_callback);
if (AudioDriverWeb::is_available()) {
-#ifdef NO_THREADS
- audio_drivers.push_back(memnew(AudioDriverScriptProcessor));
-#endif
audio_drivers.push_back(memnew(AudioDriverWorklet));
}
for (int i = 0; i < audio_drivers.size(); i++) {
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index b060d314ba..06182d921c 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -261,12 +261,7 @@ void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) {
BakeThreadsArgs *args = memnew(BakeThreadsArgs);
args->nav_region = this;
- if (p_on_thread && !OS::get_singleton()->can_use_threads()) {
- WARN_PRINT("NavigationMesh bake 'on_thread' will be disabled as the current OS does not support multiple threads."
- "\nAs a fallback the navigation mesh will bake on the main thread which can cause framerate issues.");
- }
-
- if (p_on_thread && OS::get_singleton()->can_use_threads()) {
+ if (p_on_thread) {
bake_thread.start(_bake_navigation_mesh, args);
} else {
_bake_navigation_mesh(args);
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 67a36240a2..fa8df48412 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -44,15 +44,12 @@ void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
return;
}
- if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
- if (!get_parent() || !is_visible_in_tree() || is_disabled()) {
- return;
- }
-
- if (popup->activate_item_by_event(p_event, false)) {
- accept_event();
- }
+ if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
+ accept_event();
+ return;
}
+
+ Button::shortcut_input(p_event);
}
void MenuButton::_popup_visibility_changed(bool p_visible) {
@@ -91,6 +88,18 @@ void MenuButton::pressed() {
return;
}
+ show_popup();
+}
+
+PopupMenu *MenuButton::get_popup() const {
+ return popup;
+}
+
+void MenuButton::show_popup() {
+ if (!get_viewport()) {
+ return;
+ }
+
emit_signal(SNAME("about_to_popup"));
Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
@@ -116,14 +125,6 @@ void MenuButton::pressed() {
popup->popup();
}
-void MenuButton::gui_input(const Ref<InputEvent> &p_event) {
- BaseButton::gui_input(p_event);
-}
-
-PopupMenu *MenuButton::get_popup() const {
- return popup;
-}
-
void MenuButton::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
@@ -226,6 +227,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
void MenuButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
+ ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts);
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index 97c0d21f1e..3aacbca3a8 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -44,8 +44,6 @@ class MenuButton : public Button {
Vector2i mouse_pos_adjusted;
- virtual void gui_input(const Ref<InputEvent> &p_event) override;
-
void _popup_visibility_changed(bool p_visible);
protected:
@@ -60,6 +58,8 @@ public:
virtual void pressed() override;
PopupMenu *get_popup() const;
+ void show_popup();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 08f5e0bbfb..2cbece69f2 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -231,32 +231,7 @@ void OptionButton::pressed() {
return;
}
- Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
- popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
- popup->set_size(Size2(size.width, 0));
-
- // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused.
- if (current != NONE_SELECTED && !popup->is_item_disabled(current)) {
- if (!_was_pressed_by_mouse()) {
- popup->set_focused_item(current);
- } else {
- popup->scroll_to_item(current);
- }
- } else {
- for (int i = 0; i < popup->get_item_count(); i++) {
- if (!popup->is_item_disabled(i)) {
- if (!_was_pressed_by_mouse()) {
- popup->set_focused_item(i);
- } else {
- popup->scroll_to_item(i);
- }
-
- break;
- }
- }
- }
-
- popup->popup();
+ show_popup();
}
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) {
@@ -511,6 +486,39 @@ PopupMenu *OptionButton::get_popup() const {
return popup;
}
+void OptionButton::show_popup() {
+ if (!get_viewport()) {
+ return;
+ }
+
+ Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
+ popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
+ popup->set_size(Size2(size.width, 0));
+
+ // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused.
+ if (current != NONE_SELECTED && !popup->is_item_disabled(current)) {
+ if (!_was_pressed_by_mouse()) {
+ popup->set_focused_item(current);
+ } else {
+ popup->scroll_to_item(current);
+ }
+ } else {
+ for (int i = 0; i < popup->get_item_count(); i++) {
+ if (!popup->is_item_disabled(i)) {
+ if (!_was_pressed_by_mouse()) {
+ popup->set_focused_item(i);
+ } else {
+ popup->scroll_to_item(i);
+ }
+
+ break;
+ }
+ }
+ }
+
+ popup->popup();
+}
+
void OptionButton::get_translatable_strings(List<String> *p_strings) const {
popup->get_translatable_strings(p_strings);
}
@@ -548,6 +556,7 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_select_int", "idx"), &OptionButton::_select_int);
ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup);
+ ClassDB::bind_method(D_METHOD("show_popup"), &OptionButton::show_popup);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 2c7e0510f5..b76a31d37e 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -123,6 +123,7 @@ public:
void remove_item(int p_idx);
PopupMenu *get_popup() const;
+ void show_popup();
virtual void get_translatable_strings(List<String> *p_strings) const override;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 549cc19cb1..05b4f181c2 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2799,7 +2799,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local
}
// Shortcut Input.
- if (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr) {
+ if (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr || Object::cast_to<InputEventJoypadButton>(*ev) != nullptr) {
get_tree()->_call_input_pause(shortcut_input_group, SceneTree::CALL_INPUT_TYPE_SHORTCUT_INPUT, ev, this);
}
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
index fff6dbc32a..b2e57c9a01 100644
--- a/servers/audio/effects/audio_effect_record.cpp
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -116,19 +116,11 @@ void AudioEffectRecordInstance::init() {
recording_data.clear(); //Clear data completely and reset length
is_recording = true;
-#ifdef NO_THREADS
- AudioServer::get_singleton()->add_update_callback(&AudioEffectRecordInstance::_update, this);
-#else
io_thread.start(_thread_callback, this);
-#endif
}
void AudioEffectRecordInstance::finish() {
-#ifdef NO_THREADS
- AudioServer::get_singleton()->remove_update_callback(&AudioEffectRecordInstance::_update, this);
-#else
io_thread.wait_to_finish();
-#endif
}
AudioEffectRecordInstance::~AudioEffectRecordInstance() {
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index cde241f231..1e70f6880c 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -230,6 +230,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
float sh[9 * 4];
};
+ // When changing any of these enums, remember to change the corresponding enums in the shader files as well.
enum {
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,
INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5,
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 1b31d2749d..a53872fc88 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -371,7 +371,7 @@ protected:
/* Geometry instance */
- // check which ones of these apply, probably all except GI and SDFGI
+ // When changing any of these enums, remember to change the corresponding enums in the shader files as well.
enum {
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,
INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5,
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
index e8e2dce990..0d36b98645 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
@@ -62,13 +62,14 @@ layout(set = 0, binding = 3) uniform sampler decal_sampler;
layout(set = 0, binding = 4) uniform sampler light_projector_sampler;
-#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5)
-#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6)
-#define INSTANCE_FLAGS_USE_SDFGI (1 << 7)
-#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8)
-#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9)
-#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10)
-#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11)
+#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 4)
+#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 5)
+#define INSTANCE_FLAGS_USE_SDFGI (1 << 6)
+#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 7)
+#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 8)
+#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9)
+#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10)
+#define INSTANCE_FLAGS_PARTICLES (1 << 11)
#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
index 5e4999fa0f..631ff0575b 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
@@ -55,13 +55,14 @@ layout(set = 0, binding = 2) uniform sampler shadow_sampler;
layout(set = 0, binding = 3) uniform sampler decal_sampler;
layout(set = 0, binding = 4) uniform sampler light_projector_sampler;
-#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5)
-#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6)
-#define INSTANCE_FLAGS_USE_SDFGI (1 << 7)
-#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8)
-#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9)
-#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10)
-#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11)
+#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 4)
+#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 5)
+#define INSTANCE_FLAGS_USE_SDFGI (1 << 6)
+#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 7)
+#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 8)
+#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9)
+#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10)
+#define INSTANCE_FLAGS_PARTICLES (1 << 11)
#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index ab5b8af794..1d83cc9de7 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -32,8 +32,8 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "servers/rendering/rendering_server_globals.h"
#include "servers/rendering/shader_types.h"
-#include "servers/rendering_server.h"
#define SL ShaderLanguage
@@ -1348,8 +1348,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
return code;
}
-ShaderLanguage::DataType ShaderCompiler::_get_variable_type(const StringName &p_type) {
- RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_type);
+ShaderLanguage::DataType ShaderCompiler::_get_global_shader_uniform_type(const StringName &p_name) {
+ RS::GlobalShaderParameterType gvt = RSG::material_storage->global_shader_parameter_get_type(p_name);
return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
}
@@ -1358,7 +1358,7 @@ Error ShaderCompiler::compile(RS::ShaderMode p_mode, const String &p_code, Ident
info.functions = ShaderTypes::get_singleton()->get_functions(p_mode);
info.render_modes = ShaderTypes::get_singleton()->get_modes(p_mode);
info.shader_types = ShaderTypes::get_singleton()->get_types();
- info.global_shader_uniform_type_func = _get_variable_type;
+ info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
Error err = parser.compile(p_code, info);
diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h
index 1ad43daf5f..4cbe93afb2 100644
--- a/servers/rendering/shader_compiler.h
+++ b/servers/rendering/shader_compiler.h
@@ -122,7 +122,7 @@ private:
DefaultIdentifierActions actions;
- static ShaderLanguage::DataType _get_variable_type(const StringName &p_type);
+ static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_name);
public:
Error compile(RS::ShaderMode p_mode, const String &p_code, IdentifierActions *p_actions, const String &p_path, GeneratedCode &r_gen_code);
diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h
index 52118fa943..be271bad1f 100644
--- a/tests/core/math/test_vector3.h
+++ b/tests/core/math/test_vector3.h
@@ -84,16 +84,12 @@ TEST_CASE("[Vector3] Axis methods") {
vector.min_axis_index() == Vector3::Axis::AXIS_X,
"Vector3 min_axis_index should work as expected.");
CHECK_MESSAGE(
- vector.get_axis(vector.max_axis_index()) == (real_t)5.6,
- "Vector3 get_axis should work as expected.");
+ vector[vector.max_axis_index()] == (real_t)5.6,
+ "Vector3 array operator should work as expected.");
CHECK_MESSAGE(
vector[vector.min_axis_index()] == (real_t)1.2,
"Vector3 array operator should work as expected.");
- vector.set_axis(Vector3::Axis::AXIS_Y, 4.7);
- CHECK_MESSAGE(
- vector.get_axis(Vector3::Axis::AXIS_Y) == (real_t)4.7,
- "Vector3 set_axis should work as expected.");
vector[Vector3::Axis::AXIS_Y] = 3.7;
CHECK_MESSAGE(
vector[Vector3::Axis::AXIS_Y] == (real_t)3.7,
diff --git a/tests/core/math/test_vector3i.h b/tests/core/math/test_vector3i.h
index 6c52781556..2050b222d0 100644
--- a/tests/core/math/test_vector3i.h
+++ b/tests/core/math/test_vector3i.h
@@ -53,16 +53,12 @@ TEST_CASE("[Vector3i] Axis methods") {
vector.min_axis_index() == Vector3i::Axis::AXIS_X,
"Vector3i min_axis_index should work as expected.");
CHECK_MESSAGE(
- vector.get_axis(vector.max_axis_index()) == 3,
- "Vector3i get_axis should work as expected.");
+ vector[vector.max_axis_index()] == 3,
+ "Vector3i array operator should work as expected.");
CHECK_MESSAGE(
vector[vector.min_axis_index()] == 1,
"Vector3i array operator should work as expected.");
- vector.set_axis(Vector3i::Axis::AXIS_Y, 4);
- CHECK_MESSAGE(
- vector.get_axis(Vector3i::Axis::AXIS_Y) == 4,
- "Vector3i set_axis should work as expected.");
vector[Vector3i::Axis::AXIS_Y] = 5;
CHECK_MESSAGE(
vector[Vector3i::Axis::AXIS_Y] == 5,
diff --git a/tests/core/math/test_vector4.h b/tests/core/math/test_vector4.h
index 25ec8929b8..3f50f16635 100644
--- a/tests/core/math/test_vector4.h
+++ b/tests/core/math/test_vector4.h
@@ -55,16 +55,12 @@ TEST_CASE("[Vector4] Axis methods") {
vector.min_axis_index() == Vector4::Axis::AXIS_W,
"Vector4 min_axis_index should work as expected.");
CHECK_MESSAGE(
- vector.get_axis(vector.max_axis_index()) == (real_t)5.6,
- "Vector4 get_axis should work as expected.");
+ vector[vector.max_axis_index()] == (real_t)5.6,
+ "Vector4 array operator should work as expected.");
CHECK_MESSAGE(
vector[vector.min_axis_index()] == (real_t)-0.9,
"Vector4 array operator should work as expected.");
- vector.set_axis(Vector4::Axis::AXIS_Y, 4.7);
- CHECK_MESSAGE(
- vector.get_axis(Vector4::Axis::AXIS_Y) == (real_t)4.7,
- "Vector4 set_axis should work as expected.");
vector[Vector4::Axis::AXIS_Y] = 3.7;
CHECK_MESSAGE(
vector[Vector4::Axis::AXIS_Y] == (real_t)3.7,
diff --git a/tests/core/math/test_vector4i.h b/tests/core/math/test_vector4i.h
index e106099914..309162c3f7 100644
--- a/tests/core/math/test_vector4i.h
+++ b/tests/core/math/test_vector4i.h
@@ -53,16 +53,12 @@ TEST_CASE("[Vector4i] Axis methods") {
vector.min_axis_index() == Vector4i::Axis::AXIS_X,
"Vector4i min_axis_index should work as expected.");
CHECK_MESSAGE(
- vector.get_axis(vector.max_axis_index()) == 4,
- "Vector4i get_axis should work as expected.");
+ vector[vector.max_axis_index()] == 4,
+ "Vector4i array operator should work as expected.");
CHECK_MESSAGE(
vector[vector.min_axis_index()] == 1,
"Vector4i array operator should work as expected.");
- vector.set_axis(Vector4i::Axis::AXIS_Y, 5);
- CHECK_MESSAGE(
- vector.get_axis(Vector4i::Axis::AXIS_Y) == 5,
- "Vector4i set_axis should work as expected.");
vector[Vector4i::Axis::AXIS_Y] = 5;
CHECK_MESSAGE(
vector[Vector4i::Axis::AXIS_Y] == 5,
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index d97da05c04..969f5fc096 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -740,6 +740,14 @@ TEST_CASE("[String] sprintf") {
REQUIRE(error == false);
CHECK(output == String("fish 99.990000 frog"));
+ // Real (infinity) left-padded
+ format = "fish %11f frog";
+ args.clear();
+ args.push_back(INFINITY);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish inf frog"));
+
// Real right-padded.
format = "fish %-11f frog";
args.clear();
@@ -840,6 +848,14 @@ TEST_CASE("[String] sprintf") {
REQUIRE(error == false);
CHECK(output == String("fish ( 19.990000, 1.000000, -2.050000) frog"));
+ // Vector left-padded with inf/nan
+ format = "fish %11v frog";
+ args.clear();
+ args.push_back(Variant(Vector2(INFINITY, NAN)));
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish ( inf, nan) frog"));
+
// Vector right-padded.
format = "fish %-11v frog";
args.clear();
diff --git a/tests/core/templates/test_command_queue.h b/tests/core/templates/test_command_queue.h
index 0d016f5d06..db1e436ed9 100644
--- a/tests/core/templates/test_command_queue.h
+++ b/tests/core/templates/test_command_queue.h
@@ -38,8 +38,6 @@
#include "core/templates/command_queue_mt.h"
#include "tests/test_macros.h"
-#if !defined(NO_THREADS)
-
namespace TestCommandQueue {
class ThreadWork {
@@ -426,6 +424,4 @@ TEST_CASE("[Stress][CommandQueue] Stress test command queue") {
}
} // namespace TestCommandQueue
-#endif // !defined(NO_THREADS)
-
#endif // TEST_COMMAND_QUEUE_H