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/object/object.h22
-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/rid_owner.h11
-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/DirAccess.xml4
-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/dir_access_unix.cpp12
-rw-r--r--drivers/unix/dir_access_unix.h4
-rw-r--r--drivers/unix/file_access_unix.cpp23
-rw-r--r--drivers/unix/file_access_unix.h4
-rw-r--r--drivers/unix/net_socket_posix.cpp28
-rw-r--r--drivers/unix/os_unix.cpp5
-rw-r--r--drivers/unix/os_unix.h2
-rw-r--r--drivers/unix/thread_posix.cpp4
-rw-r--r--drivers/unix/thread_posix.h2
-rw-r--r--drivers/windows/dir_access_windows.cpp28
-rw-r--r--editor/editor_fonts.cpp22
-rw-r--r--editor/editor_inspector.cpp22
-rw-r--r--editor/editor_inspector.h4
-rw-r--r--editor/editor_log.cpp25
-rw-r--r--editor/editor_resource_picker.cpp2
-rw-r--r--editor/editor_spin_slider.cpp2
-rw-r--r--editor/import/collada.cpp8
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp19
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.h1
-rw-r--r--editor/plugins/script_editor_plugin.cpp97
-rw-r--r--editor/plugins/script_editor_plugin.h3
-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--editor/scene_tree_editor.cpp4
-rw-r--r--main/main.cpp6
-rw-r--r--methods.py6
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp61
-rw-r--r--modules/gdscript/gdscript_parser.cpp3
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp32
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h1
-rw-r--r--modules/gdscript/language_server/godot_lsp.h2
-rw-r--r--modules/gltf/config.py1
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml13
-rw-r--r--modules/gltf/doc_classes/GLTFTexture.xml3
-rw-r--r--modules/gltf/doc_classes/GLTFTextureSampler.xml25
-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/gltf/gltf_defines.h2
-rw-r--r--modules/gltf/gltf_document.cpp138
-rw-r--r--modules/gltf/gltf_document.h9
-rw-r--r--modules/gltf/gltf_state.cpp11
-rw-r--r--modules/gltf/gltf_state.h6
-rw-r--r--modules/gltf/register_types.cpp2
-rw-r--r--modules/gltf/structures/gltf_texture.cpp11
-rw-r--r--modules/gltf/structures/gltf_texture.h3
-rw-r--r--modules/gltf/structures/gltf_texture_sampler.cpp47
-rw-r--r--modules/gltf/structures/gltf_texture_sampler.h163
-rw-r--r--modules/mono/csharp_script.h10
-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/android/detect.py16
-rw-r--r--platform/linuxbsd/display_server_x11.cpp20
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp4
-rw-r--r--platform/macos/dir_access_macos.h10
-rw-r--r--platform/macos/dir_access_macos.mm4
-rw-r--r--platform/uwp/os_uwp.h2
-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--platform/windows/display_server_windows.cpp2
-rw-r--r--platform/windows/display_server_windows.h2
-rw-r--r--platform/windows/os_windows.h3
-rw-r--r--scene/3d/mesh_instance_3d.cpp30
-rw-r--r--scene/3d/mesh_instance_3d.h1
-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/gui/scroll_container.cpp2
-rw-r--r--scene/main/node.cpp10
-rw-r--r--scene/main/viewport.cpp14
-rw-r--r--scene/main/viewport.h1
-rw-r--r--scene/resources/visual_shader.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
151 files changed, 3050 insertions, 3088 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/object/object.h b/core/object/object.h
index 8ade5a204a..33a5afc9aa 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -733,34 +733,12 @@ public:
template <class T>
static T *cast_to(Object *p_object) {
-#ifndef NO_SAFE_CAST
return dynamic_cast<T *>(p_object);
-#else
- if (!p_object) {
- return nullptr;
- }
- if (p_object->is_class_ptr(T::get_class_ptr_static())) {
- return static_cast<T *>(p_object);
- } else {
- return nullptr;
- }
-#endif
}
template <class T>
static const T *cast_to(const Object *p_object) {
-#ifndef NO_SAFE_CAST
return dynamic_cast<const T *>(p_object);
-#else
- if (!p_object) {
- return nullptr;
- }
- if (p_object->is_class_ptr(T::get_class_ptr_static())) {
- return static_cast<const T *>(p_object);
- } else {
- return nullptr;
- }
-#endif
}
enum {
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/rid_owner.h b/core/templates/rid_owner.h
index 320faebe98..a83ffa575f 100644
--- a/core/templates/rid_owner.h
+++ b/core/templates/rid_owner.h
@@ -335,15 +335,8 @@ public:
~RID_Alloc() {
if (alloc_count) {
- if (description) {
- print_error("ERROR: " + itos(alloc_count) + " RID allocations of type '" + description + "' were leaked at exit.");
- } else {
-#ifdef NO_SAFE_CAST
- print_error("ERROR: " + itos(alloc_count) + " RID allocations of type 'unknown' were leaked at exit.");
-#else
- print_error("ERROR: " + itos(alloc_count) + " RID allocations of type '" + typeid(T).name() + "' were leaked at exit.");
-#endif
- }
+ print_error(vformat("ERROR: %d RID allocations of type '%s' were leaked at exit.",
+ alloc_count, description ? description : typeid(T).name()));
for (size_t i = 0; i < max_alloc; i++) {
uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk];
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/DirAccess.xml b/doc/classes/DirAccess.xml
index 554ef9e2da..7d1612e59c 100644
--- a/doc/classes/DirAccess.xml
+++ b/doc/classes/DirAccess.xml
@@ -80,7 +80,7 @@
<param index="2" name="chmod_flags" type="int" default="-1" />
<description>
Copies the [param from] file to the [param to] destination. Both arguments should be paths to files, either relative or absolute. If the destination file exists and is not access-protected, it will be overwritten.
- If [param chmod_flags] is different than [code]-1[/code], the unix permissions for the destination path will be set to the provided value, if available on the current operating system.
+ If [param chmod_flags] is different than [code]-1[/code], the Unix permissions for the destination path will be set to the provided value, if available on the current operating system.
Returns one of the [enum Error] code constants ([code]OK[/code] on success).
</description>
</method>
@@ -199,7 +199,7 @@
<method name="get_space_left">
<return type="int" />
<description>
- On UNIX desktop systems, returns the available space on the current directory's disk. On other platforms, this information is not available and the method returns 0 or -1.
+ Returns the available space on the current directory's disk, in bytes. Returns [code]0[/code] if the platform-specific method to query the available space fails.
</description>
</method>
<method name="list_dir_begin">
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/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index 55ea952696..c25ceba44d 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -30,7 +30,7 @@
#include "dir_access_unix.h"
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
#include "core/os/memory.h"
#include "core/os/os.h"
@@ -41,10 +41,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
-#ifndef ANDROID_ENABLED
#include <sys/statvfs.h>
-#endif
#ifdef HAVE_MNTENT
#include <mntent.h>
@@ -475,17 +472,12 @@ Error DirAccessUnix::create_link(String p_source, String p_target) {
}
uint64_t DirAccessUnix::get_space_left() {
-#ifndef NO_STATVFS
struct statvfs vfs;
if (statvfs(current_dir.utf8().get_data(), &vfs) != 0) {
return 0;
}
return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize;
-#else
- // FIXME: Implement this.
- return 0;
-#endif
}
String DirAccessUnix::get_filesystem_type() const {
@@ -516,4 +508,4 @@ DirAccessUnix::~DirAccessUnix() {
list_dir_end();
}
-#endif // UNIX_ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX_ENABLED
diff --git a/drivers/unix/dir_access_unix.h b/drivers/unix/dir_access_unix.h
index f5dca7c282..4db24a27b9 100644
--- a/drivers/unix/dir_access_unix.h
+++ b/drivers/unix/dir_access_unix.h
@@ -31,7 +31,7 @@
#ifndef DIR_ACCESS_UNIX_H
#define DIR_ACCESS_UNIX_H
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
#include "core/io/dir_access.h"
@@ -90,6 +90,6 @@ public:
~DirAccessUnix();
};
-#endif // UNIX_ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX_ENABLED
#endif // DIR_ACCESS_UNIX_H
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index 300fbcdcfd..0df645c4e5 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -30,24 +30,20 @@
#include "file_access_unix.h"
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
#include "core/os/os.h"
#include "core/string/print_string.h"
+#include <errno.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <errno.h>
-
#if defined(UNIX_ENABLED)
#include <unistd.h>
#endif
-#ifndef ANDROID_ENABLED
-#include <sys/statvfs.h>
-#endif
-
#ifdef MSVC
#define S_ISREG(m) ((m)&_S_IFREG)
#include <io.h>
@@ -56,12 +52,6 @@
#define S_ISREG(m) ((m)&S_IFREG)
#endif
-#ifndef NO_FCNTL
-#include <fcntl.h>
-#else
-#include <sys/ioctl.h>
-#endif
-
void FileAccessUnix::check_errors() const {
ERR_FAIL_COND_MSG(!f, "File must be opened before use.");
@@ -131,13 +121,8 @@ Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) {
int fd = fileno(f);
if (fd != -1) {
-#if defined(NO_FCNTL)
- unsigned long par = 0;
- ioctl(fd, FIOCLEX, &par);
-#else
int opts = fcntl(fd, F_GETFD);
fcntl(fd, F_SETFD, opts | FD_CLOEXEC);
-#endif
}
last_error = OK;
@@ -339,4 +324,4 @@ FileAccessUnix::~FileAccessUnix() {
_close();
}
-#endif // UNIX_ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX_ENABLED
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index e1311a80f8..8c9afe75e7 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -36,7 +36,7 @@
#include <stdio.h>
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
typedef void (*CloseNotificationFunc)(const String &p_file, int p_flags);
@@ -86,6 +86,6 @@ public:
virtual ~FileAccessUnix();
};
-#endif // UNIX_ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX_ENABLED
#endif // FILE_ACCESS_UNIX_H
diff --git a/drivers/unix/net_socket_posix.cpp b/drivers/unix/net_socket_posix.cpp
index 86adf33d62..72ae609fb4 100644
--- a/drivers/unix/net_socket_posix.cpp
+++ b/drivers/unix/net_socket_posix.cpp
@@ -30,32 +30,30 @@
#include "net_socket_posix.h"
+// Some proprietary Unix-derived platforms don't expose Unix sockets
+// so this allows skipping this file to reimplement this API differently.
#ifndef UNIX_SOCKET_UNAVAILABLE
+
#if defined(UNIX_ENABLED)
#include <errno.h>
+#include <fcntl.h>
#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
-#ifndef NO_FCNTL
-#include <fcntl.h>
-#else
-#include <sys/ioctl.h>
-#endif
-#include <netinet/in.h>
-#include <sys/socket.h>
#ifdef WEB_ENABLED
#include <arpa/inet.h>
#endif
-#include <netinet/tcp.h>
-
// BSD calls this flag IPV6_JOIN_GROUP
#if !defined(IPV6_ADD_MEMBERSHIP) && defined(IPV6_JOIN_GROUP)
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
@@ -93,7 +91,7 @@
#define SIO_UDP_NETRESET _WSAIOW(IOC_VENDOR, 15)
#endif
-#endif
+#endif // UNIX_ENABLED
size_t NetSocketPosix::_set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type) {
memset(p_addr, 0, sizeof(struct sockaddr_storage));
@@ -309,14 +307,9 @@ void NetSocketPosix::_set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_
void NetSocketPosix::_set_close_exec_enabled(bool p_enabled) {
#ifndef WINDOWS_ENABLED
// Enable close on exec to avoid sharing with subprocesses. Off by default on Windows.
-#if defined(NO_FCNTL)
- unsigned long par = p_enabled ? 1 : 0;
- SOCK_IOCTL(_sock, FIOCLEX, &par);
-#else
int opts = fcntl(_sock, F_GETFD);
fcntl(_sock, F_SETFD, opts | FD_CLOEXEC);
#endif
-#endif
}
Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
@@ -658,7 +651,7 @@ void NetSocketPosix::set_blocking_enabled(bool p_enabled) {
ERR_FAIL_COND(!is_open());
int ret = 0;
-#if defined(WINDOWS_ENABLED) || defined(NO_FCNTL)
+#if defined(WINDOWS_ENABLED)
unsigned long par = p_enabled ? 0 : 1;
ret = SOCK_IOCTL(_sock, FIONBIO, &par);
#else
@@ -781,4 +774,5 @@ Error NetSocketPosix::join_multicast_group(const IPAddress &p_multi_address, Str
Error NetSocketPosix::leave_multicast_group(const IPAddress &p_multi_address, String p_if_name) {
return _change_multicast_group(p_multi_address, p_if_name, false);
}
-#endif
+
+#endif // UNIX_SOCKET_UNAVAILABLE
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index c8a42e925e..748b48d316 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);
@@ -137,10 +135,8 @@ void OS_Unix::initialize_core() {
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
-#ifndef NO_NETWORK
NetSocketPosix::make_default();
IPUnix::make_default();
-#endif
_setup_clock();
}
@@ -170,6 +166,7 @@ Error OS_Unix::get_entropy(uint8_t *r_buffer, int p_bytes) {
left -= chunk;
ofs += chunk;
} while (left > 0);
+// Define this yourself if you don't want to fall back to /dev/urandom.
#elif !defined(NO_URANDOM)
int r = open("/dev/urandom", O_RDONLY);
ERR_FAIL_COND_V(r < 0, FAILED);
diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h
index b35f161524..ce81ca0aca 100644
--- a/drivers/unix/os_unix.h
+++ b/drivers/unix/os_unix.h
@@ -53,7 +53,7 @@ public:
virtual String get_stdin_string(bool p_block) override;
- virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override; // Should return cryptographycally-safe random bytes.
+ virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
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/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 7dd294c7a1..3660e956e3 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -309,39 +309,13 @@ Error DirAccessWindows::remove(String p_path) {
}
}
-/*
-
-FileType DirAccessWindows::get_file_type(const String& p_file) const {
- WCHAR real_current_dir_name[2048];
- GetCurrentDirectoryW(2048, real_current_dir_name);
- String prev_dir = Strong::utf16((const char16_t *)real_current_dir_name);
-
- bool worked = SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
-
- DWORD attr;
- if (worked) {
- WIN32_FILE_ATTRIBUTE_DATA fileInfo;
- attr = GetFileAttributesExW((LPCWSTR)(p_file.utf16().get_data()), GetFileExInfoStandard, &fileInfo);
- }
-
- SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
-
- if (!worked) {
- return FILE_TYPE_NONE;
- }
-
- return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FILE_TYPE_
-}
-
-*/
-
uint64_t DirAccessWindows::get_space_left() {
uint64_t bytes = 0;
if (!GetDiskFreeSpaceEx(nullptr, (PULARGE_INTEGER)&bytes, nullptr, nullptr)) {
return 0;
}
- //this is either 0 or a value in bytes.
+ // This is either 0 or a value in bytes.
return bytes;
}
diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp
index c31d13d122..0262284cdf 100644
--- a/editor/editor_fonts.cpp
+++ b/editor/editor_fonts.cpp
@@ -317,6 +317,24 @@ void editor_register_fonts(Ref<Theme> p_theme) {
mono_other_fc->set_opentype_features(ftrs);
}
+ // Use fake bold/italics to style the editor log's `print_rich()` output.
+ // Use stronger embolden strength to make bold easier to distinguish from regular text.
+ Ref<FontVariation> mono_other_fc_bold = mono_other_fc->duplicate();
+ mono_other_fc_bold->set_variation_embolden(0.8);
+
+ Ref<FontVariation> mono_other_fc_italic = mono_other_fc->duplicate();
+ mono_other_fc_italic->set_variation_transform(Transform2D(1.0, 0.2, 0.0, 1.0, 0.0, 0.0));
+
+ Ref<FontVariation> mono_other_fc_bold_italic = mono_other_fc->duplicate();
+ mono_other_fc_bold_italic->set_variation_embolden(0.8);
+ mono_other_fc_bold_italic->set_variation_transform(Transform2D(1.0, 0.2, 0.0, 1.0, 0.0, 0.0));
+
+ Ref<FontVariation> mono_other_fc_mono = mono_other_fc->duplicate();
+ // Use a different font style to distinguish `[code]` in rich prints.
+ // This emulates the "faint" styling used in ANSI escape codes by using a slightly thinner font.
+ mono_other_fc_mono->set_variation_embolden(-0.25);
+ mono_other_fc_mono->set_variation_transform(Transform2D(1.0, 0.1, 0.0, 1.0, 0.0, 0.0));
+
Ref<FontVariation> italic_fc = default_fc->duplicate();
italic_fc->set_variation_transform(Transform2D(1.0, 0.2, 0.0, 1.0, 0.0, 0.0));
@@ -386,6 +404,10 @@ void editor_register_fonts(Ref<Theme> p_theme) {
p_theme->set_font_size("output_source_size", "EditorFonts", int(EDITOR_GET("run/output/font_size")) * EDSCALE);
p_theme->set_font("output_source", "EditorFonts", mono_other_fc);
+ p_theme->set_font("output_source_bold", "EditorFonts", mono_other_fc_bold);
+ p_theme->set_font("output_source_italic", "EditorFonts", mono_other_fc_italic);
+ p_theme->set_font("output_source_bold_italic", "EditorFonts", mono_other_fc_bold_italic);
+ p_theme->set_font("output_source_mono", "EditorFonts", mono_other_fc_mono);
p_theme->set_font_size("status_source_size", "EditorFonts", default_font_size);
p_theme->set_font("status_source", "EditorFonts", mono_other_fc);
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_log.cpp b/editor/editor_log.cpp
index 6e6a898757..0f2543f708 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -65,19 +65,34 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f
}
void EditorLog::_update_theme() {
- Ref<Font> normal_font = get_theme_font(SNAME("output_source"), SNAME("EditorFonts"));
+ const Ref<Font> normal_font = get_theme_font(SNAME("output_source"), SNAME("EditorFonts"));
if (normal_font.is_valid()) {
log->add_theme_font_override("normal_font", normal_font);
}
- log->add_theme_font_size_override("normal_font_size", get_theme_font_size(SNAME("output_source_size"), SNAME("EditorFonts")));
- log->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
-
- Ref<Font> bold_font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ const Ref<Font> bold_font = get_theme_font(SNAME("output_source_bold"), SNAME("EditorFonts"));
if (bold_font.is_valid()) {
log->add_theme_font_override("bold_font", bold_font);
}
+ const Ref<Font> italics_font = get_theme_font(SNAME("output_source_italic"), SNAME("EditorFonts"));
+ if (italics_font.is_valid()) {
+ log->add_theme_font_override("italics_font", italics_font);
+ }
+
+ const Ref<Font> bold_italics_font = get_theme_font(SNAME("output_source_bold_italic"), SNAME("EditorFonts"));
+ if (bold_italics_font.is_valid()) {
+ log->add_theme_font_override("bold_italics_font", bold_italics_font);
+ }
+
+ const Ref<Font> mono_font = get_theme_font(SNAME("output_source_mono"), SNAME("EditorFonts"));
+ if (mono_font.is_valid()) {
+ log->add_theme_font_override("mono_font", mono_font);
+ }
+
+ log->add_theme_font_size_override("normal_font_size", get_theme_font_size(SNAME("output_source_size"), SNAME("EditorFonts")));
+ log->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
+
type_filter_map[MSG_TYPE_STD]->toggle_button->set_icon(get_theme_icon(SNAME("Popup"), SNAME("EditorIcons")));
type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_icon(get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons")));
type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_icon(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")));
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/import/collada.cpp b/editor/import/collada.cpp
index 5d8e453395..7cf35c519b 100644
--- a/editor/import/collada.cpp
+++ b/editor/import/collada.cpp
@@ -2036,11 +2036,7 @@ void Collada::_merge_skeletons(VisualScene *p_vscene, Node *p_node) {
ERR_CONTINUE(!state.scene_map.has(nodeid)); //weird, it should have it...
-#ifdef NO_SAFE_CAST
- NodeJoint *nj = static_cast<NodeJoint *>(state.scene_map[nodeid]);
-#else
NodeJoint *nj = dynamic_cast<NodeJoint *>(state.scene_map[nodeid]);
-#endif
ERR_CONTINUE(!nj); //broken collada
ERR_CONTINUE(!nj->owner); //weird, node should have a skeleton owner
@@ -2197,11 +2193,7 @@ bool Collada::_move_geometry_to_skeletons(VisualScene *p_vscene, Node *p_node, L
String nodeid = ng->skeletons[0];
ERR_FAIL_COND_V(!state.scene_map.has(nodeid), false); //weird, it should have it...
-#ifdef NO_SAFE_CAST
- NodeJoint *nj = static_cast<NodeJoint *>(state.scene_map[nodeid]);
-#else
NodeJoint *nj = dynamic_cast<NodeJoint *>(state.scene_map[nodeid]);
-#endif
ERR_FAIL_COND_V(!nj, false);
ERR_FAIL_COND_V(!nj->owner, false); //weird, node should have a skeleton owner
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index c502d47669..d5cdb70ccf 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -270,6 +270,24 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
case MENU_OPTION_CREATE_OUTLINE_MESH: {
outline_dialog->popup_centered(Vector2(200, 90));
} break;
+ case MENU_OPTION_CREATE_DEBUG_TANGENTS: {
+ Ref<EditorUndoRedoManager> ur = EditorNode::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Create Debug Tangents"));
+
+ MeshInstance3D *tangents = node->create_debug_tangents_node();
+
+ if (tangents) {
+ Node *owner = get_tree()->get_edited_scene_root();
+
+ ur->add_do_reference(tangents);
+ ur->add_do_method(node, "add_child", tangents, true);
+ ur->add_do_method(tangents, "set_owner", owner);
+
+ ur->add_undo_method(node, "remove_child", tangents);
+ }
+
+ ur->commit_action();
+ } break;
case MENU_OPTION_CREATE_UV2: {
Ref<ArrayMesh> mesh2 = node->get_mesh();
if (!mesh2.is_valid()) {
@@ -511,6 +529,7 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Create Outline Mesh..."), MENU_OPTION_CREATE_OUTLINE_MESH);
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a static outline mesh. The outline mesh will have its normals flipped automatically.\nThis can be used instead of the StandardMaterial Grow property when using that property isn't possible."));
+ options->get_popup()->add_item(TTR("Create Debug Tangents"), MENU_OPTION_CREATE_DEBUG_TANGENTS);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("View UV1"), MENU_OPTION_DEBUG_UV1);
options->get_popup()->add_item(TTR("View UV2"), MENU_OPTION_DEBUG_UV2);
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h
index 7968176744..88800227d1 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.h
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.h
@@ -46,6 +46,7 @@ class MeshInstance3DEditor : public Control {
MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES,
MENU_OPTION_CREATE_NAVMESH,
MENU_OPTION_CREATE_OUTLINE_MESH,
+ MENU_OPTION_CREATE_DEBUG_TANGENTS,
MENU_OPTION_CREATE_UV2,
MENU_OPTION_DEBUG_UV1,
MENU_OPTION_DEBUG_UV2,
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 18561fe3a0..a3f2c2509f 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();
}
@@ -1299,26 +1301,15 @@ void ScriptEditor::_menu_option(int p_option) {
break;
}
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
- }
- }
+ if (script.is_valid()) {
+ clear_docs_from_script(script);
}
EditorNode::get_singleton()->push_item(resource.ptr());
EditorNode::get_singleton()->save_resource_as(resource);
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
- }
+ if (script.is_valid()) {
+ update_docs_from_script(script);
}
} break;
@@ -1611,7 +1602,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 +2125,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;
@@ -2426,14 +2407,8 @@ void ScriptEditor::save_current_script() {
return;
}
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
- }
- }
+ if (script.is_valid()) {
+ clear_docs_from_script(script);
}
if (resource->is_built_in()) {
@@ -2448,13 +2423,8 @@ void ScriptEditor::save_current_script() {
EditorNode::get_singleton()->save_resource(resource);
}
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
- }
+ if (script.is_valid()) {
+ update_docs_from_script(script);
}
}
@@ -2499,25 +2469,14 @@ void ScriptEditor::save_all_scripts() {
continue;
}
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
- }
- }
+ if (script.is_valid()) {
+ clear_docs_from_script(script);
}
EditorNode::get_singleton()->save_resource(edited_res); //external script, save it
- if (script != nullptr) {
- Vector<DocData::ClassDoc> documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
- }
+ if (script.is_valid()) {
+ update_docs_from_script(script);
}
} else {
// For built-in scripts, save their scenes instead.
@@ -2817,7 +2776,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) {
@@ -3340,6 +3298,29 @@ void ScriptEditor::update_doc(const String &p_name) {
}
}
+void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) {
+ ERR_FAIL_COND(p_script.is_null());
+
+ Vector<DocData::ClassDoc> documentations = p_script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
+ EditorHelp::get_doc_data()->remove_doc(doc.name);
+ }
+ }
+}
+
+void ScriptEditor::update_docs_from_script(const Ref<Script> &p_script) {
+ ERR_FAIL_COND(p_script.is_null());
+
+ Vector<DocData::ClassDoc> documentations = p_script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ EditorHelp::get_doc_data()->add_doc(doc);
+ update_doc(doc.name);
+ }
+}
+
void ScriptEditor::_update_selected_editor_menu() {
for (int i = 0; i < tab_container->get_tab_count(); i++) {
bool current = tab_container->get_current_tab() == i;
@@ -3614,7 +3595,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 +3677,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..aab713c9a3 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);
@@ -509,6 +508,8 @@ public:
void goto_help(const String &p_desc) { _help_class_goto(p_desc); }
void update_doc(const String &p_name);
+ void clear_docs_from_script(const Ref<Script> &p_script);
+ void update_docs_from_script(const Ref<Script> &p_script);
bool can_take_away_focus() const;
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..ef3f451079
--- /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_APPLICATION_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/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index 7bcc8aa078..689570bcf6 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -804,6 +804,10 @@ void SceneTreeEditor::_cell_multi_selected(Object *p_object, int p_cell, bool p_
TreeItem *item = Object::cast_to<TreeItem>(p_object);
ERR_FAIL_COND(!item);
+ if (!item->is_visible()) {
+ return;
+ }
+
NodePath np = item->get_metadata(0);
Node *n = get_node(np);
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..bec1b803e9 100644
--- a/methods.py
+++ b/methods.py
@@ -1,4 +1,5 @@
import os
+import sys
import re
import glob
import subprocess
@@ -741,7 +742,7 @@ def generate_vs_project(env, num_jobs):
PLATFORMS = ["Win32", "x64"]
PLATFORM_IDS = ["x86_32", "x86_64"]
CONFIGURATIONS = ["editor", "template_release", "template_debug"]
- DEV_SUFFIX = ["", ".dev"]
+ DEV_SUFFIX = ".dev" if env["dev_build"] else ""
@staticmethod
def for_every_variant(value):
@@ -776,10 +777,9 @@ def generate_vs_project(env, num_jobs):
for platform in ModuleConfigs.PLATFORMS
]
self.arg_dict["runfile"] += [
- f'bin\\godot.windows.{config}{dev}.{plat_id}{f".{name}" if name else ""}.exe'
+ f'bin\\godot.windows.{config}{ModuleConfigs.DEV_SUFFIX}.{plat_id}{f".{name}" if name else ""}.exe'
for config in ModuleConfigs.CONFIGURATIONS
for plat_id in ModuleConfigs.PLATFORM_IDS
- for dev in ModuleConfigs.DEV_SUFFIX
]
self.arg_dict["cpppaths"] += ModuleConfigs.for_every_variant(env["CPPPATH"] + [includes])
self.arg_dict["cppdefines"] += ModuleConfigs.for_every_variant(env["CPPDEFINES"] + defines)
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/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index f1c841e9dc..980a946e23 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -2932,13 +2932,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Allow for trailing comma.
break;
}
+ bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
} else {
call->arguments.push_back(argument);
- if (argument->type == Node::IDENTIFIER && current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+ if (argument->type == Node::IDENTIFIER && use_identifier_completion) {
completion_context.type = COMPLETION_IDENTIFIER;
}
}
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index ccde0521f2..189e7fcc71 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -42,6 +42,7 @@ void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
+ ClassDB::bind_method(D_METHOD("willSaveWaitUntil"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
@@ -81,6 +82,16 @@ void GDScriptTextDocument::didChange(const Variant &p_param) {
sync_script_content(doc.uri, doc.text);
}
+void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
+ Ref<Script> script = ResourceLoader::load(path);
+ if (script.is_valid()) {
+ ScriptEditor::get_singleton()->clear_docs_from_script(script);
+ }
+}
+
void GDScriptTextDocument::didSave(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
@@ -88,11 +99,16 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
sync_script_content(doc.uri, text);
- /*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
-
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
Ref<GDScript> script = ResourceLoader::load(path);
- script->load_source_code(path);
- script->reload(true);*/
+ if (script.is_valid() && (script->load_source_code(path) == OK)) {
+ if (script->is_tool()) {
+ script->get_language()->reload_tool_script(script, true);
+ } else {
+ script->reload(true);
+ }
+ ScriptEditor::get_singleton()->update_docs_from_script(script);
+ }
}
lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
@@ -417,14 +433,6 @@ void GDScriptTextDocument::sync_script_content(const String &p_path, const Strin
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
EditorFileSystem::get_singleton()->update_file(path);
- Error error;
- Ref<GDScript> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error);
- if (error == OK) {
- if (script->load_source_code(path) == OK) {
- script->reload(true);
- ScriptEditor::get_singleton()->reload_scripts(true); // Refresh scripts opened in the internal editor.
- }
- }
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index 87bc08a34e..456c7d71fe 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -45,6 +45,7 @@ protected:
void didOpen(const Variant &p_param);
void didClose(const Variant &p_param);
void didChange(const Variant &p_param);
+ void willSaveWaitUntil(const Variant &p_param);
void didSave(const Variant &p_param);
void sync_script_content(const String &p_path, const String &p_content);
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index fbd40796c4..024da1cab7 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -546,7 +546,7 @@ struct TextDocumentSyncOptions {
* If present will save wait until requests are sent to the server. If omitted the request should not be
* sent.
*/
- bool willSaveWaitUntil = false;
+ bool willSaveWaitUntil = true;
/**
* If present save notifications are sent to the server. If omitted the notification should not be
diff --git a/modules/gltf/config.py b/modules/gltf/config.py
index 189b5a831a..130c06d264 100644
--- a/modules/gltf/config.py
+++ b/modules/gltf/config.py
@@ -26,6 +26,7 @@ def get_doc_classes():
"GLTFSpecGloss",
"GLTFState",
"GLTFTexture",
+ "GLTFTextureSampler",
]
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index 6c2f488c1c..56f3a70631 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -93,6 +93,12 @@
<description>
</description>
</method>
+ <method name="get_texture_samplers">
+ <return type="GLTFTextureSampler[]" />
+ <description>
+ Retrieves the array of texture samplers that are used by the textures contained in the GLTF.
+ </description>
+ </method>
<method name="get_textures">
<return type="GLTFTexture[]" />
<description>
@@ -180,6 +186,13 @@
<description>
</description>
</method>
+ <method name="set_texture_samplers">
+ <return type="void" />
+ <param index="0" name="texture_samplers" type="GLTFTextureSampler[]" />
+ <description>
+ Sets the array of texture samplers that are used by the textures contained in the GLTF.
+ </description>
+ </method>
<method name="set_textures">
<return type="void" />
<param index="0" name="textures" type="GLTFTexture[]" />
diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml
index c0bc424168..f4486fd504 100644
--- a/modules/gltf/doc_classes/GLTFTexture.xml
+++ b/modules/gltf/doc_classes/GLTFTexture.xml
@@ -7,6 +7,9 @@
<tutorials>
</tutorials>
<members>
+ <member name="sampler" type="int" setter="set_sampler" getter="get_sampler" default="-1">
+ ID of the texture sampler to use when sampling the image. If -1, then the default texture sampler is used (linear filtering, and repeat wrapping in both axes).
+ </member>
<member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="0">
</member>
</members>
diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml
new file mode 100644
index 0000000000..1cc69bfe73
--- /dev/null
+++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFTextureSampler" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Represents a GLTF texture sampler
+ </brief_description>
+ <description>
+ Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object.
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" default="9729">
+ Texture's magnification filter, used when texture appears larger on screen than the source image.
+ </member>
+ <member name="min_filter" type="int" setter="set_min_filter" getter="get_min_filter" default="9987">
+ Texture's minification filter, used when the texture appears smaller on screen than the source image.
+ </member>
+ <member name="wrap_s" type="int" setter="set_wrap_s" getter="get_wrap_s" default="10497">
+ Wrapping mode to use for S-axis (horizontal) texture coordinates.
+ </member>
+ <member name="wrap_t" type="int" setter="set_wrap_t" getter="get_wrap_t" default="10497">
+ Wrapping mode to use for T-axis (vertical) texture coordinates.
+ </member>
+ </members>
+</class>
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/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h
index 9ee2397968..23bf33869e 100644
--- a/modules/gltf/gltf_defines.h
+++ b/modules/gltf/gltf_defines.h
@@ -58,6 +58,7 @@ class GLTFSkin;
class GLTFSpecGloss;
class GLTFState;
class GLTFTexture;
+class GLTFTextureSampler;
// GLTF index aliases.
using GLTFAccessorIndex = int;
@@ -73,6 +74,7 @@ using GLTFNodeIndex = int;
using GLTFSkeletonIndex = int;
using GLTFSkinIndex = int;
using GLTFTextureIndex = int;
+using GLTFTextureSamplerIndex = int;
enum GLTFType {
TYPE_SCALAR,
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 8d2e37be3a..35358734d6 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -145,6 +145,12 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) {
return Error::FAILED;
}
+ /* STEP SERIALIZE TEXTURE SAMPLERS */
+ err = _serialize_texture_samplers(state);
+ if (err != OK) {
+ return Error::FAILED;
+ }
+
/* STEP SERIALIZE ANIMATIONS */
err = _serialize_animations(state);
if (err != OK) {
@@ -3219,6 +3225,11 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) {
Ref<GLTFTexture> t = state->textures[i];
ERR_CONTINUE(t->get_src_image() == -1);
d["source"] = t->get_src_image();
+
+ GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+ if (sampler_index != -1) {
+ d["sampler"] = sampler_index;
+ }
textures.push_back(d);
}
state->json["textures"] = textures;
@@ -3240,13 +3251,18 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> state) {
Ref<GLTFTexture> t;
t.instantiate();
t->set_src_image(d["source"]);
+ if (d.has("sampler")) {
+ t->set_sampler(d["sampler"]);
+ } else {
+ t->set_sampler(-1);
+ }
state->textures.push_back(t);
}
return OK;
}
-GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture) {
+GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
ERR_FAIL_COND_V(p_texture.is_null(), -1);
Ref<GLTFTexture> gltf_texture;
gltf_texture.instantiate();
@@ -3254,6 +3270,7 @@ GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D>
GLTFImageIndex gltf_src_image_i = state->images.size();
state->images.push_back(p_texture);
gltf_texture->set_src_image(gltf_src_image_i);
+ gltf_texture->set_sampler(_set_sampler_for_mode(state, p_filter_mode, p_repeats));
GLTFTextureIndex gltf_texture_i = state->textures.size();
state->textures.push_back(gltf_texture);
return gltf_texture_i;
@@ -3268,6 +3285,102 @@ Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextur
return state->images[image];
}
+GLTFTextureSamplerIndex GLTFDocument::_set_sampler_for_mode(Ref<GLTFState> state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
+ for (int i = 0; i < state->texture_samplers.size(); ++i) {
+ if (state->texture_samplers[i]->get_filter_mode() == p_filter_mode) {
+ return i;
+ }
+ }
+
+ GLTFTextureSamplerIndex gltf_sampler_i = state->texture_samplers.size();
+ Ref<GLTFTextureSampler> gltf_sampler;
+ gltf_sampler.instantiate();
+ gltf_sampler->set_filter_mode(p_filter_mode);
+ gltf_sampler->set_wrap_mode(p_repeats);
+ state->texture_samplers.push_back(gltf_sampler);
+ return gltf_sampler_i;
+}
+
+Ref<GLTFTextureSampler> GLTFDocument::_get_sampler_for_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) {
+ ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>());
+ const GLTFTextureSamplerIndex sampler = state->textures[p_texture]->get_sampler();
+
+ if (sampler == -1) {
+ return state->default_texture_sampler;
+ } else {
+ ERR_FAIL_INDEX_V(sampler, state->texture_samplers.size(), Ref<GLTFTextureSampler>());
+
+ return state->texture_samplers[sampler];
+ }
+}
+
+Error GLTFDocument::_serialize_texture_samplers(Ref<GLTFState> state) {
+ if (!state->texture_samplers.size()) {
+ return OK;
+ }
+
+ Array samplers;
+ for (int32_t i = 0; i < state->texture_samplers.size(); ++i) {
+ Dictionary d;
+ Ref<GLTFTextureSampler> s = state->texture_samplers[i];
+ d["magFilter"] = s->get_mag_filter();
+ d["minFilter"] = s->get_min_filter();
+ d["wrapS"] = s->get_wrap_s();
+ d["wrapT"] = s->get_wrap_t();
+ samplers.push_back(d);
+ }
+ state->json["samplers"] = samplers;
+
+ return OK;
+}
+
+Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> state) {
+ state->default_texture_sampler.instantiate();
+ state->default_texture_sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+ state->default_texture_sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+ state->default_texture_sampler->set_wrap_s(GLTFTextureSampler::WrapMode::REPEAT);
+ state->default_texture_sampler->set_wrap_t(GLTFTextureSampler::WrapMode::REPEAT);
+
+ if (!state->json.has("samplers")) {
+ return OK;
+ }
+
+ const Array &samplers = state->json["samplers"];
+ for (int i = 0; i < samplers.size(); ++i) {
+ const Dictionary &d = samplers[i];
+
+ Ref<GLTFTextureSampler> sampler;
+ sampler.instantiate();
+
+ if (d.has("minFilter")) {
+ sampler->set_min_filter(d["minFilter"]);
+ } else {
+ sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+ }
+ if (d.has("magFilter")) {
+ sampler->set_mag_filter(d["magFilter"]);
+ } else {
+ sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+ }
+
+ if (d.has("wrapS")) {
+ sampler->set_wrap_s(d["wrapS"]);
+ } else {
+ sampler->set_wrap_s(GLTFTextureSampler::WrapMode::DEFAULT);
+ }
+
+ if (d.has("wrapT")) {
+ sampler->set_wrap_t(d["wrapT"]);
+ } else {
+ sampler->set_wrap_t(GLTFTextureSampler::WrapMode::DEFAULT);
+ }
+
+ state->texture_samplers.push_back(sampler);
+ }
+
+ return OK;
+}
+
Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
Array materials;
for (int32_t i = 0; i < state->materials.size(); i++) {
@@ -3299,7 +3412,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
albedo_texture->set_name(material->get_name() + "_albedo");
- gltf_texture_index = _set_texture(state, albedo_texture);
+ gltf_texture_index = _set_texture(state, albedo_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
bct["index"] = gltf_texture_index;
@@ -3429,7 +3542,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex orm_texture_index = -1;
if (has_ao || has_roughness || has_metalness) {
orm_texture->set_name(material->get_name() + "_orm");
- orm_texture_index = _set_texture(state, orm_texture);
+ orm_texture_index = _set_texture(state, orm_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (has_ao) {
Dictionary occt;
@@ -3484,7 +3597,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex gltf_texture_index = -1;
if (tex.is_valid() && tex->get_image().is_valid()) {
tex->set_name(material->get_name() + "_normal");
- gltf_texture_index = _set_texture(state, tex);
+ gltf_texture_index = _set_texture(state, tex, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
nt["scale"] = material->get_normal_scale();
if (gltf_texture_index != -1) {
@@ -3507,7 +3620,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex gltf_texture_index = -1;
if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
emission_texture->set_name(material->get_name() + "_emission");
- gltf_texture_index = _set_texture(state, emission_texture);
+ gltf_texture_index = _set_texture(state, emission_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
@@ -3566,6 +3679,11 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
if (sgm.has("diffuseTexture")) {
const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"];
if (diffuse_texture_dict.has("index")) {
+ Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(state, diffuse_texture_dict["index"]);
+ if (diffuse_sampler.is_valid()) {
+ material->set_texture_filter(diffuse_sampler->get_filter_mode());
+ material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, diffuse_sampler->get_wrap_mode());
+ }
Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]);
if (diffuse_texture.is_valid()) {
spec_gloss->diffuse_img = diffuse_texture->get_image();
@@ -3614,6 +3732,9 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
if (mr.has("baseColorTexture")) {
const Dictionary &bct = mr["baseColorTexture"];
if (bct.has("index")) {
+ Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(state, bct["index"]);
+ material->set_texture_filter(bct_sampler->get_filter_mode());
+ material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, bct_sampler->get_wrap_mode());
material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(state, bct["index"]));
}
if (!mr.has("baseColorFactor")) {
@@ -6477,8 +6598,10 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess>
err = ext->import_preflight(state);
ERR_FAIL_COND_V(err != OK, err);
}
+
err = _parse_gltf_state(state, p_path, p_bake_fps);
ERR_FAIL_COND_V(err != OK, err);
+
return OK;
}
@@ -6834,6 +6957,11 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+ /* PARSE TEXTURE SAMPLERS */
+ err = _parse_texture_samplers(state);
+
+ ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+
/* PARSE TEXTURES */
err = _parse_textures(state);
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 750d3d403e..62b6e29fe0 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -95,9 +95,14 @@ private:
String _gen_unique_bone_name(Ref<GLTFState> state,
const GLTFSkeletonIndex skel_i,
const String &p_name);
- GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture);
+ GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture,
+ StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
Ref<Texture2D> _get_texture(Ref<GLTFState> state,
const GLTFTextureIndex p_texture);
+ GLTFTextureSamplerIndex _set_sampler_for_mode(Ref<GLTFState> state,
+ StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
+ Ref<GLTFTextureSampler> _get_sampler_for_texture(Ref<GLTFState> state,
+ const GLTFTextureIndex p_texture);
Error _parse_json(const String &p_path, Ref<GLTFState> state);
Error _parse_glb(Ref<FileAccess> f, Ref<GLTFState> state);
void _compute_node_heights(Ref<GLTFState> state);
@@ -145,10 +150,12 @@ private:
const bool p_for_vertex);
Error _parse_meshes(Ref<GLTFState> state);
Error _serialize_textures(Ref<GLTFState> state);
+ Error _serialize_texture_samplers(Ref<GLTFState> state);
Error _serialize_images(Ref<GLTFState> state, const String &p_path);
Error _serialize_lights(Ref<GLTFState> state);
Error _parse_images(Ref<GLTFState> state, const String &p_base_path);
Error _parse_textures(Ref<GLTFState> state);
+ Error _parse_texture_samplers(Ref<GLTFState> state);
Error _parse_materials(Ref<GLTFState> state);
void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material);
void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss,
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index a23fb39503..60192c67e6 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -64,6 +64,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes);
ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures);
ClassDB::bind_method(D_METHOD("set_textures", "textures"), &GLTFState::set_textures);
+ ClassDB::bind_method(D_METHOD("get_texture_samplers"), &GLTFState::get_texture_samplers);
+ ClassDB::bind_method(D_METHOD("set_texture_samplers", "texture_samplers"), &GLTFState::set_texture_samplers);
ClassDB::bind_method(D_METHOD("get_images"), &GLTFState::get_images);
ClassDB::bind_method(D_METHOD("set_images", "images"), &GLTFState::set_images);
ClassDB::bind_method(D_METHOD("get_skins"), &GLTFState::get_skins);
@@ -101,6 +103,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_path"), "set_base_path", "get_base_path"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_samplers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_texture_samplers", "get_texture_samplers"); //Vector<Ref<GLTFTextureSampler>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector<Ref<Texture>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector<Ref<GLTFSkin>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector<Ref<GLTFCamera>>
@@ -236,6 +239,14 @@ void GLTFState::set_textures(TypedArray<GLTFTexture> p_textures) {
GLTFTemplateConvert::set_from_array(textures, p_textures);
}
+TypedArray<GLTFTextureSampler> GLTFState::get_texture_samplers() {
+ return GLTFTemplateConvert::to_array(texture_samplers);
+}
+
+void GLTFState::set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers) {
+ GLTFTemplateConvert::set_from_array(texture_samplers, p_texture_samplers);
+}
+
TypedArray<Texture2D> GLTFState::get_images() {
return GLTFTemplateConvert::to_array(images);
}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 791431f376..afe7e82010 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -42,6 +42,7 @@
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
#include "core/templates/rb_map.h"
#include "scene/animation/animation_player.h"
@@ -77,6 +78,8 @@ class GLTFState : public Resource {
String scene_name;
Vector<int> root_nodes;
Vector<Ref<GLTFTexture>> textures;
+ Vector<Ref<GLTFTextureSampler>> texture_samplers;
+ Ref<GLTFTextureSampler> default_texture_sampler;
Vector<Ref<Texture2D>> images;
Vector<String> extensions_used;
Vector<String> extensions_required;
@@ -149,6 +152,9 @@ public:
TypedArray<GLTFTexture> get_textures();
void set_textures(TypedArray<GLTFTexture> p_textures);
+ TypedArray<GLTFTextureSampler> get_texture_samplers();
+ void set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers);
+
TypedArray<Texture2D> get_images();
void set_images(TypedArray<Texture2D> p_images);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 6e7f7d6fed..b9027f6e3d 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -47,6 +47,7 @@
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
@@ -126,6 +127,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFSpecGloss);
GDREGISTER_CLASS(GLTFState);
GDREGISTER_CLASS(GLTFTexture);
+ GDREGISTER_CLASS(GLTFTextureSampler);
}
#ifdef TOOLS_ENABLED
diff --git a/modules/gltf/structures/gltf_texture.cpp b/modules/gltf/structures/gltf_texture.cpp
index 2a21cb3df8..5cc96e3221 100644
--- a/modules/gltf/structures/gltf_texture.cpp
+++ b/modules/gltf/structures/gltf_texture.cpp
@@ -33,8 +33,11 @@
void GLTFTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_src_image"), &GLTFTexture::get_src_image);
ClassDB::bind_method(D_METHOD("set_src_image", "src_image"), &GLTFTexture::set_src_image);
+ ClassDB::bind_method(D_METHOD("get_sampler"), &GLTFTexture::get_sampler);
+ ClassDB::bind_method(D_METHOD("set_sampler", "sampler"), &GLTFTexture::set_sampler);
ADD_PROPERTY(PropertyInfo(Variant::INT, "src_image"), "set_src_image", "get_src_image"); // int
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sampler"), "set_sampler", "get_sampler"); // int
}
GLTFImageIndex GLTFTexture::get_src_image() const {
@@ -44,3 +47,11 @@ GLTFImageIndex GLTFTexture::get_src_image() const {
void GLTFTexture::set_src_image(GLTFImageIndex val) {
src_image = val;
}
+
+GLTFTextureSamplerIndex GLTFTexture::get_sampler() const {
+ return sampler;
+}
+
+void GLTFTexture::set_sampler(GLTFTextureSamplerIndex val) {
+ sampler = val;
+}
diff --git a/modules/gltf/structures/gltf_texture.h b/modules/gltf/structures/gltf_texture.h
index b1d12dddfa..b26c8a7b07 100644
--- a/modules/gltf/structures/gltf_texture.h
+++ b/modules/gltf/structures/gltf_texture.h
@@ -39,6 +39,7 @@ class GLTFTexture : public Resource {
private:
GLTFImageIndex src_image = 0;
+ GLTFTextureSamplerIndex sampler = -1;
protected:
static void _bind_methods();
@@ -46,6 +47,8 @@ protected:
public:
GLTFImageIndex get_src_image() const;
void set_src_image(GLTFImageIndex val);
+ GLTFTextureSamplerIndex get_sampler() const;
+ void set_sampler(GLTFTextureSamplerIndex val);
};
#endif // GLTF_TEXTURE_H
diff --git a/modules/gltf/structures/gltf_texture_sampler.cpp b/modules/gltf/structures/gltf_texture_sampler.cpp
new file mode 100644
index 0000000000..6cf615b6cc
--- /dev/null
+++ b/modules/gltf/structures/gltf_texture_sampler.cpp
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* gltf_texture_sampler.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 "gltf_texture_sampler.h"
+
+void GLTFTextureSampler::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_mag_filter"), &GLTFTextureSampler::get_mag_filter);
+ ClassDB::bind_method(D_METHOD("set_mag_filter", "filter_mode"), &GLTFTextureSampler::set_mag_filter);
+ ClassDB::bind_method(D_METHOD("get_min_filter"), &GLTFTextureSampler::get_min_filter);
+ ClassDB::bind_method(D_METHOD("set_min_filter", "filter_mode"), &GLTFTextureSampler::set_min_filter);
+ ClassDB::bind_method(D_METHOD("get_wrap_s"), &GLTFTextureSampler::get_wrap_s);
+ ClassDB::bind_method(D_METHOD("set_wrap_s", "wrap_mode"), &GLTFTextureSampler::set_wrap_s);
+ ClassDB::bind_method(D_METHOD("get_wrap_t"), &GLTFTextureSampler::get_wrap_t);
+ ClassDB::bind_method(D_METHOD("set_wrap_t", "wrap_mode"), &GLTFTextureSampler::set_wrap_t);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "mag_filter"), "set_mag_filter", "get_mag_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "min_filter"), "set_min_filter", "get_min_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_s"), "set_wrap_s", "get_wrap_s");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_t"), "set_wrap_t", "get_wrap_t");
+}
diff --git a/modules/gltf/structures/gltf_texture_sampler.h b/modules/gltf/structures/gltf_texture_sampler.h
new file mode 100644
index 0000000000..3fad31bbee
--- /dev/null
+++ b/modules/gltf/structures/gltf_texture_sampler.h
@@ -0,0 +1,163 @@
+/*************************************************************************/
+/* gltf_texture_sampler.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 GLTF_TEXTURE_SAMPLER_H
+#define GLTF_TEXTURE_SAMPLER_H
+
+#include "core/io/resource.h"
+#include "scene/resources/material.h"
+
+class GLTFTextureSampler : public Resource {
+ GDCLASS(GLTFTextureSampler, Resource);
+
+public:
+ enum FilterMode {
+ NEAREST = 9728,
+ LINEAR = 9729,
+ NEAREST_MIPMAP_NEAREST = 9984,
+ LINEAR_MIPMAP_NEAREST = 9985,
+ NEAREST_MIPMAP_LINEAR = 9986,
+ LINEAR_MIPMAP_LINEAR = 9987
+ };
+
+ enum WrapMode {
+ CLAMP_TO_EDGE = 33071,
+ MIRRORED_REPEAT = 33648,
+ REPEAT = 10497,
+ DEFAULT = REPEAT
+ };
+
+ int get_mag_filter() const {
+ return mag_filter;
+ }
+
+ void set_mag_filter(const int filter_mode) {
+ mag_filter = (FilterMode)filter_mode;
+ }
+
+ int get_min_filter() const {
+ return min_filter;
+ }
+
+ void set_min_filter(const int filter_mode) {
+ min_filter = (FilterMode)filter_mode;
+ }
+
+ int get_wrap_s() const {
+ return wrap_s;
+ }
+
+ void set_wrap_s(const int wrap_mode) {
+ wrap_s = (WrapMode)wrap_mode;
+ }
+
+ int get_wrap_t() const {
+ return wrap_t;
+ }
+
+ void set_wrap_t(const int wrap_mode) {
+ wrap_s = (WrapMode)wrap_mode;
+ }
+
+ StandardMaterial3D::TextureFilter get_filter_mode() const {
+ using TextureFilter = StandardMaterial3D::TextureFilter;
+
+ switch (min_filter) {
+ case NEAREST:
+ return TextureFilter::TEXTURE_FILTER_NEAREST;
+ case LINEAR:
+ return TextureFilter::TEXTURE_FILTER_LINEAR;
+ case NEAREST_MIPMAP_NEAREST:
+ case NEAREST_MIPMAP_LINEAR:
+ return TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;
+ case LINEAR_MIPMAP_NEAREST:
+ case LINEAR_MIPMAP_LINEAR:
+ default:
+ return TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;
+ }
+ }
+
+ void set_filter_mode(StandardMaterial3D::TextureFilter mode) {
+ using TextureFilter = StandardMaterial3D::TextureFilter;
+
+ switch (mode) {
+ case TextureFilter::TEXTURE_FILTER_NEAREST:
+ min_filter = FilterMode::NEAREST;
+ mag_filter = FilterMode::NEAREST;
+ break;
+ case TextureFilter::TEXTURE_FILTER_LINEAR:
+ min_filter = FilterMode::LINEAR;
+ mag_filter = FilterMode::LINEAR;
+ break;
+ case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:
+ case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC:
+ min_filter = FilterMode::NEAREST_MIPMAP_LINEAR;
+ mag_filter = FilterMode::NEAREST;
+ break;
+ case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:
+ case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC:
+ default:
+ min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+ mag_filter = FilterMode::LINEAR;
+ break;
+ }
+ }
+
+ bool get_wrap_mode() const {
+ // BaseMaterial3D presents wrapping as a boolean property. Either the texture is repeated
+ // in both dimensions, non-mirrored, or it isn't repeated at all. This will cause oddities
+ // when people import models having other wrapping mode combinations.
+ return (wrap_s == WrapMode::REPEAT) && (wrap_t == WrapMode::REPEAT);
+ }
+
+ void set_wrap_mode(bool mat_repeats) {
+ if (mat_repeats) {
+ wrap_s = WrapMode::REPEAT;
+ wrap_t = WrapMode::REPEAT;
+ } else {
+ wrap_s = WrapMode::CLAMP_TO_EDGE;
+ wrap_t = WrapMode::CLAMP_TO_EDGE;
+ }
+ }
+
+protected:
+ static void _bind_methods();
+
+private:
+ FilterMode mag_filter = FilterMode::LINEAR;
+ FilterMode min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+ WrapMode wrap_s = WrapMode::REPEAT;
+ WrapMode wrap_t = WrapMode::REPEAT;
+};
+
+VARIANT_ENUM_CAST(GLTFTextureSampler::FilterMode);
+VARIANT_ENUM_CAST(GLTFTextureSampler::WrapMode);
+
+#endif // GLTF_TEXTURE_SAMPLER_H
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index d469c28d4a..e5e53acb07 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -48,20 +48,10 @@ class CSharpScript;
class CSharpInstance;
class CSharpLanguage;
-#ifdef NO_SAFE_CAST
-template <typename TScriptInstance, typename TScriptLanguage>
-TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
- if (!p_inst) {
- return nullptr;
- }
- return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr;
-}
-#else
template <typename TScriptInstance, typename TScriptLanguage>
TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
return dynamic_cast<TScriptInstance *>(p_inst);
}
-#endif
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
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/android/detect.py b/platform/android/detect.py
index e541aa0373..6eb8ba34ed 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -156,22 +156,16 @@ def configure(env: "Environment"):
env["RANLIB"] = compiler_path + "/llvm-ranlib"
env["AS"] = compiler_path + "/clang"
- # Disable exceptions and rtti on non-tools (template) builds
- if env.editor_build:
- env.Append(CXXFLAGS=["-frtti"])
- elif env["builtin_icu"]:
- env.Append(CXXFLAGS=["-frtti", "-fno-exceptions"])
- else:
- env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"])
- # Don't use dynamic_cast, necessary with no-rtti.
- env.Append(CPPDEFINES=["NO_SAFE_CAST"])
+ # Disable exceptions on template builds
+ if not env.editor_build:
+ env.Append(CXXFLAGS=["-fno-exceptions"])
env.Append(
CCFLAGS=(
"-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
)
)
- env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"])
+ env.Append(CPPDEFINES=["GLES_ENABLED"])
if get_min_sdk_version(env["ndk_platform"]) >= 24:
env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
@@ -193,7 +187,7 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so")
env.Prepend(CPPPATH=["#platform/android"])
- env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "NO_FCNTL"])
+ env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"])
env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "android", "log", "z", "dl"])
if env["vulkan"]:
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 66dea6cf1b..1ef1deb971 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -49,10 +49,14 @@
#include "drivers/gles3/rasterizer_gles3.h"
#endif
+#include <dlfcn.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
@@ -66,17 +70,6 @@
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
-#include <dlfcn.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-//stupid linux.h
-#ifdef KEY_TAB
-#undef KEY_TAB
-#endif
-
#undef CursorShape
#include <X11/XKBlib.h>
@@ -3142,6 +3135,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/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index f4e94f1a91..4cbd9722ad 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -42,12 +42,10 @@
#include <mntent.h>
#endif
+#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
-#include <dlfcn.h>
-#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
diff --git a/platform/macos/dir_access_macos.h b/platform/macos/dir_access_macos.h
index 920e69ef3e..c76b2835e8 100644
--- a/platform/macos/dir_access_macos.h
+++ b/platform/macos/dir_access_macos.h
@@ -31,16 +31,16 @@
#ifndef DIR_ACCESS_MACOS_H
#define DIR_ACCESS_MACOS_H
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
+
+#include "core/io/dir_access.h"
+#include "drivers/unix/dir_access_unix.h"
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
-#include "core/io/dir_access.h"
-#include "drivers/unix/dir_access_unix.h"
-
class DirAccessMacOS : public DirAccessUnix {
protected:
virtual String fix_unicode_name(const char *p_name) const override;
@@ -51,6 +51,6 @@ protected:
virtual bool is_hidden(const String &p_name) override;
};
-#endif // UNIX ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX ENABLED
#endif // DIR_ACCESS_MACOS_H
diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm
index 3373cada1f..22ebac2db4 100644
--- a/platform/macos/dir_access_macos.mm
+++ b/platform/macos/dir_access_macos.mm
@@ -30,7 +30,7 @@
#include "dir_access_macos.h"
-#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED)
+#if defined(UNIX_ENABLED)
#include <errno.h>
@@ -78,4 +78,4 @@ bool DirAccessMacOS::is_hidden(const String &p_name) {
return [hidden boolValue];
}
-#endif // UNIX_ENABLED || LIBC_FILEIO_ENABLED
+#endif // UNIX_ENABLED
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 0c1c4a793a..c1df7827cd 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -42,9 +42,9 @@
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering_server.h"
-#include <fcntl.h>
#include <io.h>
#include <stdio.h>
+
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
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/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 77b20dd924..9e958be26f 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -565,7 +565,7 @@ bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const {
#ifndef _MSC_VER
#warning touchscreen not working
#endif
- return false;
+ return DisplayServer::screen_is_touchscreen(p_screen);
}
void DisplayServerWindows::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index d85d6364bd..3523e8b3d1 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -61,9 +61,9 @@
#include "gl_manager_windows.h"
#endif
-#include <fcntl.h>
#include <io.h>
#include <stdio.h>
+
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 491de2266f..419871f8bf 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -40,6 +40,7 @@
#include "drivers/winmidi/midi_driver_winmidi.h"
#include "key_mapping_windows.h"
#include "servers/audio_server.h"
+
#ifdef XAUDIO2_ENABLED
#include "drivers/xaudio2/audio_driver_xaudio2.h"
#endif
@@ -49,10 +50,10 @@
#include "platform/windows/vulkan_context_win.h"
#endif
-#include <fcntl.h>
#include <io.h>
#include <shellapi.h>
#include <stdio.h>
+
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index a76f4dd0d5..b0503c9c02 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -390,13 +390,13 @@ void MeshInstance3D::_mesh_changed() {
update_gizmos();
}
-void MeshInstance3D::create_debug_tangents() {
+MeshInstance3D *MeshInstance3D::create_debug_tangents_node() {
Vector<Vector3> lines;
Vector<Color> colors;
Ref<Mesh> mesh = get_mesh();
if (!mesh.is_valid()) {
- return;
+ return nullptr;
}
for (int i = 0; i < mesh->get_surface_count(); i++) {
@@ -457,15 +457,23 @@ void MeshInstance3D::create_debug_tangents() {
MeshInstance3D *mi = memnew(MeshInstance3D);
mi->set_mesh(am);
mi->set_name("DebugTangents");
- add_child(mi, true);
-#ifdef TOOLS_ENABLED
+ return mi;
+ }
- if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) {
- mi->set_owner(this);
- } else {
- mi->set_owner(get_owner());
- }
-#endif
+ return nullptr;
+}
+
+void MeshInstance3D::create_debug_tangents() {
+ MeshInstance3D *mi = create_debug_tangents_node();
+ if (!mi) {
+ return;
+ }
+
+ add_child(mi, true);
+ if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) {
+ mi->set_owner(this);
+ } else {
+ mi->set_owner(get_owner());
}
}
@@ -495,8 +503,6 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
- ClassDB::set_method_flags("MeshInstance3D", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
-
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_GROUP("Skeleton", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index 48d76b9a88..caf7d25616 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -90,6 +90,7 @@ public:
Node *create_multiple_convex_collisions_node();
void create_multiple_convex_collisions();
+ MeshInstance3D *create_debug_tangents_node();
void create_debug_tangents();
virtual AABB get_aabb() const override;
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/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 761072c5bc..a6aa4707ff 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -162,7 +162,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
drag_accum = Vector2();
last_drag_accum = Vector2();
drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value());
- drag_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_touching_deaccel = false;
beyond_deadzone = false;
time_since_motion = 0;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index a2b0f1a825..bbdba16cb5 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1466,20 +1466,10 @@ bool Node::is_greater_than(const Node *p_node) const {
ERR_FAIL_COND_V(data.depth < 0, false);
ERR_FAIL_COND_V(p_node->data.depth < 0, false);
-#ifdef NO_ALLOCA
-
- Vector<int> this_stack;
- Vector<int> that_stack;
- this_stack.resize(data.depth);
- that_stack.resize(p_node->data.depth);
-
-#else
int *this_stack = (int *)alloca(sizeof(int) * data.depth);
int *that_stack = (int *)alloca(sizeof(int) * p_node->data.depth);
-#endif
-
const Node *n = this;
int idx = data.depth - 1;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 549cc19cb1..0795d91035 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -499,6 +499,13 @@ void Viewport::_notification(int p_what) {
// exit event if the change in focus results in the mouse exiting
// the window.
} break;
+
+ case NOTIFICATION_PREDELETE: {
+ if (gui_parent) {
+ gui_parent->gui.tooltip_popup = nullptr;
+ gui_parent->gui.tooltip_label = nullptr;
+ }
+ } break;
}
}
@@ -1155,8 +1162,6 @@ void Viewport::_gui_cancel_tooltip() {
}
if (gui.tooltip_popup) {
gui.tooltip_popup->queue_delete();
- gui.tooltip_popup = nullptr;
- gui.tooltip_label = nullptr;
}
}
@@ -1219,8 +1224,6 @@ void Viewport::_gui_show_tooltip() {
// Remove previous popup if we change something.
if (gui.tooltip_popup) {
memdelete(gui.tooltip_popup);
- gui.tooltip_popup = nullptr;
- gui.tooltip_label = nullptr;
}
if (!tooltip_owner) {
@@ -1252,6 +1255,7 @@ void Viewport::_gui_show_tooltip() {
panel->set_flag(Window::FLAG_POPUP, false);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
+ panel->gui_parent = this;
gui.tooltip_popup = panel;
@@ -2799,7 +2803,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/scene/main/viewport.h b/scene/main/viewport.h
index 471dc41246..6f67649ea3 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -208,6 +208,7 @@ private:
friend class ViewportTexture;
Viewport *parent = nullptr;
+ Viewport *gui_parent = nullptr; // Whose gui.tooltip_popup it is.
AudioListener2D *audio_listener_2d = nullptr;
Camera2D *camera_2d = nullptr;
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 320889679d..9174fcd9e3 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -955,7 +955,7 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
}
bool VisualShader::is_port_types_compatible(int p_a, int p_b) const {
- return MAX(0, p_a - 4) == (MAX(0, p_b - 4));
+ return MAX(0, p_a - 5) == (MAX(0, p_b - 5));
}
void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
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