summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/javascript_builds.yml2
-rw-r--r--core/core_bind.cpp17
-rw-r--r--core/core_bind.h3
-rw-r--r--core/extension/gdnative_interface.cpp19
-rw-r--r--core/extension/gdnative_interface.h5
-rw-r--r--core/extension/native_extension.cpp5
-rw-r--r--core/extension/native_extension.h2
-rw-r--r--core/io/multiplayer_api.cpp252
-rw-r--r--core/io/multiplayer_api.h76
-rw-r--r--core/io/multiplayer_replicator.cpp497
-rw-r--r--core/io/multiplayer_replicator.h99
-rw-r--r--core/object/make_virtuals.py2
-rw-r--r--core/object/undo_redo.cpp4
-rw-r--r--core/object/undo_redo.h4
-rw-r--r--core/os/os.cpp7
-rw-r--r--core/os/os.h3
-rw-r--r--core/register_core_types.cpp2
-rw-r--r--core/string/ustring.cpp27
-rw-r--r--core/variant/variant_parser.cpp2
-rw-r--r--doc/classes/Directory.xml2
-rw-r--r--doc/classes/MultiplayerAPI.xml91
-rw-r--r--doc/classes/MultiplayerReplicator.xml139
-rw-r--r--doc/classes/OS.xml8
-rw-r--r--doc/classes/RenderingDevice.xml4
-rw-r--r--doc/classes/String.xml15
-rw-r--r--doc/classes/TextEdit.xml12
-rw-r--r--doc/classes/Theme.xml21
-rw-r--r--doc/classes/Tree.xml3
-rw-r--r--doc/classes/TreeItem.xml18
-rw-r--r--doc/classes/UndoRedo.xml4
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp38
-rw-r--r--drivers/vulkan/rendering_device_vulkan.h3
-rw-r--r--drivers/vulkan/vulkan_context.cpp54
-rw-r--r--editor/code_editor.cpp6
-rw-r--r--editor/editor_node.cpp17
-rw-r--r--editor/editor_spin_slider.cpp314
-rw-r--r--editor/editor_spin_slider.h1
-rw-r--r--editor/editor_themes.cpp22
-rw-r--r--editor/icons/GuiIndeterminate.svg1
-rw-r--r--editor/icons/VisualShaderNodeTexture2DArrayUniform.svg (renamed from editor/icons/VisualShaderNodeTexture2DUniformTriplanar.svg)0
-rw-r--r--editor/icons/VisualShaderNodeTexture3DUniform.svg1
-rw-r--r--editor/icons/VisualShaderNodeTextureUniform.svg1
-rw-r--r--editor/icons/VisualShaderNodeTextureUniformTriplanar.svg1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp15
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/plugins/script_editor_plugin.cpp3
-rw-r--r--editor/plugins/script_editor_plugin.h1
-rw-r--r--editor/plugins/script_text_editor.cpp14
-rw-r--r--editor/plugins/script_text_editor.h1
-rw-r--r--editor/plugins/text_editor.cpp12
-rw-r--r--editor/plugins/text_editor.h1
-rw-r--r--editor/scene_tree_editor.cpp3
-rw-r--r--modules/gdscript/gdscript_cache.cpp2
-rw-r--r--platform/android/android_input_handler.cpp2
-rw-r--r--platform/android/export/export_plugin.cpp18
-rw-r--r--platform/android/export/export_plugin.h2
-rw-r--r--platform/android/export/gradle_export_util.cpp8
-rw-r--r--platform/android/export/gradle_export_util.h2
-rw-r--r--platform/android/java/app/AndroidManifest.xml1
-rw-r--r--platform/android/java/app/config.gradle8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java247
-rw-r--r--platform/android/java_godot_io_wrapper.cpp20
-rw-r--r--platform/android/java_godot_io_wrapper.h6
-rw-r--r--platform/android/os_android.cpp18
-rw-r--r--platform/android/os_android.h5
-rw-r--r--platform/javascript/js/libs/library_godot_os.js2
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp2
-rw-r--r--platform/linuxbsd/os_linuxbsd.h2
-rw-r--r--platform/osx/os_osx.h2
-rw-r--r--platform/osx/os_osx.mm2
-rw-r--r--platform/windows/os_windows.cpp2
-rw-r--r--platform/windows/os_windows.h2
-rw-r--r--scene/2d/physics_body_2d.cpp18
-rw-r--r--scene/3d/physics_body_3d.cpp10
-rw-r--r--scene/3d/skeleton_3d.cpp5
-rw-r--r--scene/gui/line_edit.cpp16
-rw-r--r--scene/gui/line_edit.h2
-rw-r--r--scene/gui/text_edit.cpp29
-rw-r--r--scene/gui/text_edit.h2
-rw-r--r--scene/gui/tree.cpp25
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/main/node.h2
-rw-r--r--scene/main/window.cpp2
-rw-r--r--scene/resources/default_theme/default_theme.cpp1
-rw-r--r--scene/resources/default_theme/indeterminate.pngbin0 -> 242 bytes
-rw-r--r--scene/resources/default_theme/theme_data.h8
-rw-r--r--scene/resources/particles_material.h7
-rw-r--r--scene/resources/skeleton_modification_2d_fabrik.cpp19
-rw-r--r--scene/resources/theme.cpp75
-rw-r--r--scene/resources/theme.h3
-rw-r--r--servers/rendering/renderer_rd/effects_rd.cpp134
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp6
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.h4
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp8
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_storage_rd.cpp10
-rw-r--r--servers/rendering/renderer_rd/renderer_storage_rd.h15
-rw-r--r--servers/rendering/renderer_rd/shader_rd.cpp2
-rw-r--r--servers/rendering/rendering_device.cpp14
-rw-r--r--servers/rendering/rendering_device.h8
-rw-r--r--tests/test_geometry_3d.h29
-rw-r--r--tests/test_string.h24
-rw-r--r--tests/test_variant.h39
108 files changed, 1704 insertions, 1041 deletions
diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml
index 7f7ebf680f..f88d14fbe9 100644
--- a/.github/workflows/javascript_builds.yml
+++ b/.github/workflows/javascript_builds.yml
@@ -6,7 +6,7 @@ env:
GODOT_BASE_BRANCH: master
SCONSFLAGS: platform=javascript verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2
SCONS_CACHE_LIMIT: 4096
- EM_VERSION: 2.0.25
+ EM_VERSION: 2.0.27
EM_CACHE_FOLDER: 'emsdk-cache'
jobs:
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index f3f51e57ee..7a2e6765b8 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -456,10 +456,6 @@ String _OS::get_user_data_dir() const {
return OS::get_singleton()->get_user_data_dir();
}
-String _OS::get_external_data_dir() const {
- return OS::get_singleton()->get_external_data_dir();
-}
-
String _OS::get_config_dir() const {
// Exposed as `get_config_dir()` instead of `get_config_path()` for consistency with other exposed OS methods.
return OS::get_singleton()->get_config_path();
@@ -483,8 +479,8 @@ bool _OS::is_debug_build() const {
#endif
}
-String _OS::get_system_dir(SystemDir p_dir) const {
- return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir));
+String _OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
+ return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir), p_shared_storage);
}
String _OS::get_keycode_string(uint32_t p_code) const {
@@ -567,8 +563,7 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &_OS::get_static_memory_peak_usage);
ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir);
- ClassDB::bind_method(D_METHOD("get_external_data_dir"), &_OS::get_external_data_dir);
- ClassDB::bind_method(D_METHOD("get_system_dir", "dir"), &_OS::get_system_dir);
+ ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &_OS::get_system_dir, DEFVAL(true));
ClassDB::bind_method(D_METHOD("get_config_dir"), &_OS::get_config_dir);
ClassDB::bind_method(D_METHOD("get_data_dir"), &_OS::get_data_dir);
ClassDB::bind_method(D_METHOD("get_cache_dir"), &_OS::get_cache_dir);
@@ -1560,15 +1555,17 @@ Error _Directory::copy(String p_from, String p_to) {
Error _Directory::rename(String p_from, String p_to) {
ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use.");
+ ERR_FAIL_COND_V_MSG(p_from.is_empty() || p_from == "." || p_from == "..", ERR_INVALID_PARAMETER, "Invalid path to rename.");
+
if (!p_from.is_rel_path()) {
DirAccess *d = DirAccess::create_for_path(p_from);
- ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist.");
+ ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist.");
Error err = d->rename(p_from, p_to);
memdelete(d);
return err;
}
- ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist.");
+ ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist.");
return d->rename(p_from, p_to);
}
diff --git a/core/core_bind.h b/core/core_bind.h
index 9060aff340..94e8ab2a34 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -229,10 +229,9 @@ public:
SYSTEM_DIR_RINGTONES,
};
- String get_system_dir(SystemDir p_dir) const;
+ String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
String get_user_data_dir() const;
- String get_external_data_dir() const;
String get_config_dir() const;
String get_data_dir() const;
String get_cache_dir() const;
diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp
index e0ebd19376..de107b4156 100644
--- a/core/extension/gdnative_interface.cpp
+++ b/core/extension/gdnative_interface.cpp
@@ -663,7 +663,23 @@ static const char32_t *gdnative_string_operator_index_const(const GDNativeString
/* OBJECT API */
-static void gdnative_object_method_bind_ptrcall(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) {
+static void gdnative_object_method_bind_call(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error) {
+ MethodBind *mb = (MethodBind *)p_method_bind;
+ Object *o = (Object *)p_instance;
+ const Variant **args = (const Variant **)p_args;
+ Callable::CallError error;
+
+ Variant ret = mb->call(o, args, p_arg_count, error);
+ memnew_placement(r_return, Variant(ret));
+
+ if (r_error) {
+ r_error->error = (GDNativeCallErrorType)(error.error);
+ r_error->argument = error.argument;
+ r_error->expected = error.expected;
+ }
+}
+
+static void gdnative_object_method_bind_ptrcall(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) {
MethodBind *mb = (MethodBind *)p_method_bind;
Object *o = (Object *)p_instance;
mb->ptrcall(o, (const void **)p_args, p_ret);
@@ -829,6 +845,7 @@ void gdnative_setup_interface(GDNativeInterface *p_interface) {
/* OBJECT */
+ gdni.object_method_bind_call = gdnative_object_method_bind_call;
gdni.object_method_bind_ptrcall = gdnative_object_method_bind_ptrcall;
gdni.object_destroy = gdnative_object_destroy;
gdni.global_get_singleton = gdnative_global_get_singleton;
diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h
index 3432d8fabe..3a5b04429c 100644
--- a/core/extension/gdnative_interface.h
+++ b/core/extension/gdnative_interface.h
@@ -389,7 +389,8 @@ typedef struct {
/* OBJECT */
- void (*object_method_bind_ptrcall)(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
+ void (*object_method_bind_call)(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_ret, GDNativeCallError *r_error);
+ void (*object_method_bind_ptrcall)(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
void (*object_destroy)(GDNativeObjectPtr p_o);
GDNativeObjectPtr (*global_get_singleton)(const char *p_name);
void *(*object_get_instance_binding)(GDNativeObjectPtr p_o, void *p_token, const GDNativeInstanceBindingCallbacks *p_callbacks);
@@ -409,7 +410,7 @@ typedef struct {
void (*classdb_register_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs);
void (*classdb_register_extension_class_method)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info);
- void (*classdb_register_extension_class_integer_constant)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_enum_name, const char *p_class_name, const char *p_constant_name, uint32_t p_constant_value);
+ void (*classdb_register_extension_class_integer_constant)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value);
void (*classdb_register_extension_class_property)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter);
void (*classdb_register_extension_class_signal)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count);
void (*classdb_unregister_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */
diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp
index 91d304627a..0556c9c84d 100644
--- a/core/extension/native_extension.cpp
+++ b/core/extension/native_extension.cpp
@@ -69,7 +69,7 @@ public:
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
Variant ret;
GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
- GDNativeCallError ce;
+ GDNativeCallError ce{ GDNATIVE_CALL_OK, 0, 0 };
call_func(method_userdata, extension_instance, (const GDNativeVariantPtr *)p_args, p_arg_count, (GDNativeVariantPtr)&ret, &ce);
r_error.error = Callable::CallError::Error(ce.error);
r_error.argument = ce.argument;
@@ -154,6 +154,7 @@ void NativeExtension::_register_extension_class(const GDNativeExtensionClassLibr
extension->native_extension.create_instance = p_extension_funcs->create_instance_func;
extension->native_extension.set_object_instance = p_extension_funcs->object_instance_func;
extension->native_extension.free_instance = p_extension_funcs->free_instance_func;
+ extension->native_extension.get_virtual = p_extension_funcs->get_virtual_func;
ClassDB::register_extension_class(&extension->native_extension);
}
@@ -171,7 +172,7 @@ void NativeExtension::_register_extension_class_method(const GDNativeExtensionCl
ClassDB::bind_method_custom(class_name, method);
}
-void NativeExtension::_register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value) {
+void NativeExtension::_register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value) {
NativeExtension *self = (NativeExtension *)p_library;
StringName class_name = p_class_name;
diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h
index 8bd95b2867..a961b21cc9 100644
--- a/core/extension/native_extension.h
+++ b/core/extension/native_extension.h
@@ -48,7 +48,7 @@ class NativeExtension : public Resource {
static void _register_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs);
static void _register_extension_class_method(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info);
- static void _register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value);
+ static void _register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value);
static void _register_extension_class_property(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter);
static void _register_extension_class_signal(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count);
static void _unregister_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name);
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp
index f792dc6cb4..0ce9a70921 100644
--- a/core/io/multiplayer_api.cpp
+++ b/core/io/multiplayer_api.cpp
@@ -32,15 +32,11 @@
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
+#include "core/io/multiplayer_replicator.h"
#include "scene/main/node.h"
-#include "scene/resources/packed_scene.h"
#include <stdint.h>
-#define NODE_ID_COMPRESSION_SHIFT 3
-#define NAME_ID_COMPRESSION_SHIFT 5
-#define BYTE_ONLY_OR_NO_ARGS_SHIFT 6
-
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#endif
@@ -147,7 +143,7 @@ void MultiplayerAPI::poll() {
}
void MultiplayerAPI::clear() {
- replicated_nodes.clear();
+ replicator->clear();
connected_peers.clear();
path_get_cache.clear();
path_send_cache.clear();
@@ -325,10 +321,10 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
_process_raw(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SPAWN: {
- _process_spawn_despawn(p_from, p_packet, p_packet_len, true);
+ replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true);
} break;
case NETWORK_COMMAND_DESPAWN: {
- _process_spawn_despawn(p_from, p_packet, p_packet_len, false);
+ replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
} break;
}
}
@@ -338,7 +334,6 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin
if (p_node_target & 0x80000000) {
// Use full path (not cached yet).
-
int ofs = p_node_target & 0x7FFFFFFF;
ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared.");
@@ -353,25 +348,11 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin
if (!node) {
ERR_PRINT("Failed to get path from RPC: " + String(np) + ".");
}
+ return node;
} else {
// Use cached path.
- int id = p_node_target;
-
- Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from);
- ERR_FAIL_COND_V_MSG(!E, nullptr, "Invalid packet received. Requests invalid peer cache.");
-
- Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id);
- ERR_FAIL_COND_V_MSG(!F, nullptr, "Invalid packet received. Unabled to find requested cached node.");
-
- PathGetCache::NodeInfo *ni = &F->get();
- // Do proper caching later.
-
- node = root_node->get_node(ni->path);
- if (!node) {
- ERR_PRINT("Failed to get cached path from RPC: " + String(ni->path) + ".");
- }
+ return get_cached_node(p_from, p_node_target);
}
- return node;
}
void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) {
@@ -425,7 +406,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id,
ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
int vlen;
- Error err = _decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen);
+ Error err = decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen);
ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument.");
argp.write[i] = &args[i];
@@ -514,64 +495,6 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet,
E->get() = true;
}
-void MultiplayerAPI::_process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) {
- ERR_FAIL_COND_MSG(p_packet_len < 18, "Invalid spawn packet received");
- int ofs = 1;
- ResourceUID::ID id = decode_uint64(&p_packet[ofs]);
- ofs += 8;
- ERR_FAIL_COND_MSG(!spawnables.has(id), "Invalid spawn ID received " + itos(id));
-
- uint32_t node_target = decode_uint32(&p_packet[ofs]);
- Node *parent = _process_get_node(p_from, nullptr, node_target, 0);
- ofs += 4;
- ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found.");
-
- uint32_t name_len = decode_uint32(&p_packet[ofs]);
- ofs += 4;
- ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len));
- ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size.");
-
- const String name = String::utf8((const char *)&p_packet[ofs], name_len);
- // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names.
- ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name));
- ofs += name_len;
- PackedByteArray data;
- if (p_packet_len > ofs) {
- data.resize(p_packet_len - ofs);
- memcpy(data.ptrw(), &p_packet[ofs], data.size());
- }
-
- SpawnMode mode = spawnables[id];
- if (mode == SPAWN_MODE_SERVER && p_from == 1) {
- String scene_path = ResourceUID::get_singleton()->get_id_path(id);
- if (p_spawn) {
- ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name));
- RES res = ResourceLoader::load(scene_path);
- ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path);
- PackedScene *scene = Object::cast_to<PackedScene>(res.ptr());
- ERR_FAIL_COND(!scene);
- Node *node = scene->instantiate();
- ERR_FAIL_COND(!node);
- replicated_nodes[node->get_instance_id()] = id;
- parent->_add_child_nocheck(node, name);
- emit_signal(SNAME("network_spawn"), p_from, id, node, data);
- } else {
- ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name));
- Node *node = parent->get_node(name);
- ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name));
- emit_signal(SNAME("network_despawn"), p_from, id, node, data);
- replicated_nodes.erase(node->get_instance_id());
- node->queue_delete();
- }
- } else {
- if (p_spawn) {
- emit_signal(SNAME("network_spawn_request"), p_from, id, parent, name, data);
- } else {
- emit_signal(SNAME("network_despawn_request"), p_from, id, parent, name, data);
- }
- }
-}
-
bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) {
bool has_all_peers = true;
List<int> peers_to_add; // If one is missing, take note to add it.
@@ -647,7 +570,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC
#define ENCODE_16 1 << 5
#define ENCODE_32 2 << 5
#define ENCODE_64 3 << 5
-Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) {
+Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) {
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
@@ -722,7 +645,7 @@ Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uin
return OK;
}
-Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) {
+Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) {
const uint8_t *buf = p_buffer;
int len = p_len;
@@ -906,10 +829,10 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const
ofs += 1;
for (int i = 0; i < p_argcount; i++) {
int len(0);
- Error err = _encode_and_compress_variant(*p_arg[i], nullptr, len);
+ Error err = encode_and_compress_variant(*p_arg[i], nullptr, len);
ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!");
MAKE_ROOM(ofs + len);
- _encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len);
+ encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len);
ofs += len;
}
}
@@ -976,14 +899,7 @@ void MultiplayerAPI::_add_peer(int p_id) {
connected_peers.insert(p_id);
path_get_cache.insert(p_id, PathGetCache());
if (is_network_server()) {
- for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) {
- // Only server mode adds to replicated_nodes, no need to check it.
- Object *obj = ObjectDB::get_instance(E.key);
- ERR_CONTINUE(!obj);
- Node *node = Object::cast_to<Node>(obj);
- ERR_CONTINUE(!node);
- _send_spawn_despawn(p_id, E.value, node->get_path(), nullptr, 0, true);
- }
+ replicator->spawn_all(p_id);
}
emit_signal(SNAME("network_peer_connected"), p_id);
}
@@ -1000,10 +916,6 @@ void MultiplayerAPI::_del_peer(int p_id) {
PathSentCache *psc = path_send_cache.getptr(E);
psc->confirmed_peers.erase(p_id);
}
- if (p_id == 1) {
- // Erase server replicated nodes, but do not queue them for deletion.
- replicated_nodes.clear();
- }
emit_signal(SNAME("network_peer_disconnected"), p_id);
}
@@ -1109,6 +1021,36 @@ void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_pac
emit_signal(SNAME("network_peer_packet"), p_from, out);
}
+bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) {
+ // See if the path is cached.
+ PathSentCache *psc = path_send_cache.getptr(p_path);
+ if (!psc) {
+ // Path is not cached, create.
+ path_send_cache[p_path] = PathSentCache();
+ psc = path_send_cache.getptr(p_path);
+ psc->id = last_send_cache_id++;
+ }
+ r_id = psc->id;
+
+ // See if all peers have cached path (if so, call can be fast).
+ return _send_confirm_path(p_node, p_path, psc, p_peer_id);
+}
+
+Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) {
+ Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from);
+ ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from));
+
+ Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id);
+ ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from));
+
+ PathGetCache::NodeInfo *ni = &F->get();
+ Node *node = root_node->get_node(ni->path);
+ if (!node) {
+ ERR_PRINT("Failed to get cached path: " + String(ni->path) + ".");
+ }
+ return node;
+}
+
int MultiplayerAPI::get_network_unique_id() const {
ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), 0, "No network peer is assigned. Unable to get unique network ID.");
return network_peer->get_unique_id();
@@ -1147,97 +1089,12 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
return allow_object_decoding;
}
-Error MultiplayerAPI::spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode) {
- ERR_FAIL_COND_V(p_mode < SPAWN_MODE_NONE || p_mode > SPAWN_MODE_CUSTOM, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER);
-#ifdef TOOLS_ENABLED
- String path = ResourceUID::get_singleton()->get_id_path(p_id);
- RES res = ResourceLoader::load(path);
- ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER);
-#endif
- if (p_mode == SPAWN_MODE_NONE) {
- if (spawnables.has(p_id)) {
- spawnables.erase(p_id);
- }
- } else {
- spawnables[p_id] = p_mode;
- }
- return OK;
-}
-
-Error MultiplayerAPI::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) {
- return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), false);
+MultiplayerReplicator *MultiplayerAPI::get_replicator() const {
+ return replicator;
}
-Error MultiplayerAPI::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) {
- return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), true);
-}
-
-Error MultiplayerAPI::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn) {
- ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED);
- ERR_FAIL_COND_V_MSG(!spawnables.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
-
- NodePath rel_path = (root_node->get_path()).rel_path_to(p_path);
- const Vector<StringName> names = rel_path.get_names();
- ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER);
-
- NodePath parent = NodePath(names.subarray(0, names.size() - 2), false);
- ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent);
-
- // See if the path is cached.
- PathSentCache *psc = path_send_cache.getptr(parent);
- if (!psc) {
- // Path is not cached, create.
- path_send_cache[parent] = PathSentCache();
- psc = path_send_cache.getptr(parent);
- psc->id = last_send_cache_id++;
- }
- _send_confirm_path(root_node->get_node(parent), parent, psc, p_peer_id);
-
- const CharString cname = String(names[names.size() - 1]).utf8();
- int nlen = encode_cstring(cname.get_data(), nullptr);
- MAKE_ROOM(1 + 8 + 4 + 4 + nlen + p_data_len);
- uint8_t *ptr = packet_cache.ptrw();
- ptr[0] = p_spawn ? NETWORK_COMMAND_SPAWN : NETWORK_COMMAND_DESPAWN;
- int ofs = 1;
- ofs += encode_uint64(p_scene_id, &ptr[ofs]);
- ofs += encode_uint32(psc->id, &ptr[ofs]);
- ofs += encode_uint32(nlen, &ptr[ofs]);
- ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
- memcpy(&ptr[ofs], p_data, p_data_len);
- network_peer->set_target_peer(p_peer_id);
- network_peer->set_transfer_channel(0);
- network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
- return network_peer->put_packet(ptr, ofs + p_data_len);
-}
-
-void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter) {
- if (!has_network_peer()) {
- return;
- }
- ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node);
- NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path());
- if (path.is_empty()) {
- return;
- }
- ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene);
- if (!spawnables.has(id)) {
- return;
- }
- SpawnMode mode = spawnables[id];
- if (p_enter) {
- if (mode == SPAWN_MODE_SERVER && is_network_server()) {
- replicated_nodes[p_node->get_instance_id()] = id;
- _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, true);
- }
- emit_signal(SNAME("network_spawnable_added"), id, p_node);
- } else {
- if (mode == SPAWN_MODE_SERVER && is_network_server() && replicated_nodes.has(p_node->get_instance_id())) {
- replicated_nodes.erase(p_node->get_instance_id());
- _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, false);
- }
- emit_signal(SNAME("network_spawnable_removed"), id, p_node);
- }
+void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
+ replicator->scene_enter_exit_notify(p_scene, p_node, p_enter);
}
void MultiplayerAPI::_bind_methods() {
@@ -1258,15 +1115,14 @@ void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding);
ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed);
- ClassDB::bind_method(D_METHOD("spawnable_config", "scene_id", "spawn_mode"), &MultiplayerAPI::spawnable_config);
- ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_despawn, DEFVAL(PackedByteArray()));
- ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_spawn, DEFVAL(PackedByteArray()));
+ ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node");
ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator");
ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id")));
@@ -1274,27 +1130,19 @@ void MultiplayerAPI::_bind_methods() {
ADD_SIGNAL(MethodInfo("connected_to_server"));
ADD_SIGNAL(MethodInfo("connection_failed"));
ADD_SIGNAL(MethodInfo("server_disconnected"));
- ADD_SIGNAL(MethodInfo("network_despawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("network_spawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("network_despawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("network_spawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("network_spawnable_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
- ADD_SIGNAL(MethodInfo("network_spawnable_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
BIND_ENUM_CONSTANT(RPC_MODE_REMOTE);
BIND_ENUM_CONSTANT(RPC_MODE_MASTER);
BIND_ENUM_CONSTANT(RPC_MODE_PUPPET);
-
- BIND_ENUM_CONSTANT(SPAWN_MODE_NONE);
- BIND_ENUM_CONSTANT(SPAWN_MODE_SERVER);
- BIND_ENUM_CONSTANT(SPAWN_MODE_CUSTOM);
}
MultiplayerAPI::MultiplayerAPI() {
+ replicator = memnew(MultiplayerReplicator(this));
clear();
}
MultiplayerAPI::~MultiplayerAPI() {
clear();
+ memdelete(replicator);
}
diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h
index 9552a0bf35..5853541efa 100644
--- a/core/io/multiplayer_api.h
+++ b/core/io/multiplayer_api.h
@@ -35,6 +35,8 @@
#include "core/io/resource_uid.h"
#include "core/object/ref_counted.h"
+class MultiplayerReplicator;
+
class MultiplayerAPI : public RefCounted {
GDCLASS(MultiplayerAPI, RefCounted);
@@ -46,12 +48,6 @@ public:
RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets
};
- enum SpawnMode {
- SPAWN_MODE_NONE,
- SPAWN_MODE_SERVER,
- SPAWN_MODE_CUSTOM,
- };
-
struct RPCConfig {
StringName name;
RPCMode rpc_mode = RPC_MODE_DISABLED;
@@ -71,6 +67,32 @@ public:
}
};
+ enum NetworkCommands {
+ NETWORK_COMMAND_REMOTE_CALL = 0,
+ NETWORK_COMMAND_SIMPLIFY_PATH,
+ NETWORK_COMMAND_CONFIRM_PATH,
+ NETWORK_COMMAND_RAW,
+ NETWORK_COMMAND_SPAWN,
+ NETWORK_COMMAND_DESPAWN,
+ };
+
+ enum NetworkNodeIdCompression {
+ NETWORK_NODE_ID_COMPRESSION_8 = 0,
+ NETWORK_NODE_ID_COMPRESSION_16,
+ NETWORK_NODE_ID_COMPRESSION_32,
+ };
+
+ enum NetworkNameIdCompression {
+ NETWORK_NAME_ID_COMPRESSION_8 = 0,
+ NETWORK_NAME_ID_COMPRESSION_16,
+ };
+
+ enum {
+ NODE_ID_COMPRESSION_SHIFT = 3,
+ NAME_ID_COMPRESSION_SHIFT = 5,
+ BYTE_ONLY_OR_NO_ARGS_SHIFT = 6,
+ };
+
private:
//path sent caches
struct PathSentCache {
@@ -89,16 +111,15 @@ private:
};
Ref<MultiplayerPeer> network_peer;
- Map<ResourceUID::ID, SpawnMode> spawnables;
int rpc_sender_id = 0;
Set<int> connected_peers;
HashMap<NodePath, PathSentCache> path_send_cache;
Map<int, PathGetCache> path_get_cache;
- Map<ObjectID, ResourceUID::ID> replicated_nodes;
int last_send_cache_id;
Vector<uint8_t> packet_cache;
Node *root_node = nullptr;
bool allow_object_decoding = false;
+ MultiplayerReplicator *replicator = nullptr;
protected:
static void _bind_methods();
@@ -106,7 +127,6 @@ protected:
void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len);
void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len);
void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len);
- void _process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len);
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
@@ -114,30 +134,7 @@ protected:
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target);
- Error _encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len);
- Error _decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len);
-
public:
- enum NetworkCommands {
- NETWORK_COMMAND_REMOTE_CALL = 0,
- NETWORK_COMMAND_SIMPLIFY_PATH,
- NETWORK_COMMAND_CONFIRM_PATH,
- NETWORK_COMMAND_RAW,
- NETWORK_COMMAND_SPAWN,
- NETWORK_COMMAND_DESPAWN,
- };
-
- enum NetworkNodeIdCompression {
- NETWORK_NODE_ID_COMPRESSION_8 = 0,
- NETWORK_NODE_ID_COMPRESSION_16,
- NETWORK_NODE_ID_COMPRESSION_32,
- };
-
- enum NetworkNameIdCompression {
- NETWORK_NAME_ID_COMPRESSION_8 = 0,
- NETWORK_NAME_ID_COMPRESSION_16,
- };
-
void poll();
void clear();
void set_root_node(Node *p_node);
@@ -146,8 +143,16 @@ public:
Ref<MultiplayerPeer> get_network_peer() const;
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
+ Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len);
+ Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len);
+
// Called by Node.rpc
void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount);
+ // Called by Node._notification
+ void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
+ // Called by replicator
+ bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id);
+ Node *get_cached_node(int p_from, uint32_t p_node_id);
void _add_peer(int p_id);
void _del_peer(int p_id);
@@ -166,17 +171,12 @@ public:
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
- Error spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode);
- Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray());
- Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray());
- Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn);
- void scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter);
+ MultiplayerReplicator *get_replicator() const;
MultiplayerAPI();
~MultiplayerAPI();
};
VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode);
-VARIANT_ENUM_CAST(MultiplayerAPI::SpawnMode);
#endif // MULTIPLAYER_API_H
diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp
new file mode 100644
index 0000000000..ba0fe32b58
--- /dev/null
+++ b/core/io/multiplayer_replicator.cpp
@@ -0,0 +1,497 @@
+/*************************************************************************/
+/* multiplayer_replicator.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 "core/io/multiplayer_replicator.h"
+
+#include "core/io/marshalls.h"
+#include "scene/main/node.h"
+#include "scene/resources/packed_scene.h"
+
+#define MAKE_ROOM(m_amount) \
+ if (packet_cache.size() < m_amount) \
+ packet_cache.resize(m_amount);
+
+Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) {
+ ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ Error err;
+ // Prepare state
+ List<Variant> state_variants;
+ int state_len = 0;
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (p_spawn) {
+ if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) {
+ return err;
+ }
+ }
+
+ bool is_raw = false;
+ if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) {
+ is_raw = true;
+ } else if (state_variants.size()) {
+ err = _encode_state(state_variants, nullptr, state_len);
+ ERR_FAIL_COND_V(err, err);
+ } else {
+ is_raw = true;
+ }
+
+ int ofs = 0;
+
+ // Prepare simplified path
+ const Node *root_node = multiplayer->get_root_node();
+ ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED);
+ NodePath rel_path = (root_node->get_path()).rel_path_to(p_path);
+ const Vector<StringName> names = rel_path.get_names();
+ ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER);
+
+ NodePath parent = NodePath(names.subarray(0, names.size() - 2), false);
+ ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent);
+
+ int path_id = 0;
+ multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id);
+
+ // Encode name and parent ID.
+ CharString cname = String(names[names.size() - 1]).utf8();
+ int nlen = encode_cstring(cname.get_data(), nullptr);
+ MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len);
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT);
+ ofs = 1;
+ ofs += encode_uint64(p_scene_id, &ptr[ofs]);
+ ofs += encode_uint32(path_id, &ptr[ofs]);
+ ofs += encode_uint32(nlen, &ptr[ofs]);
+ ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
+
+ // Encode state.
+ if (!is_raw) {
+ _encode_state(state_variants, &ptr[ofs], state_len);
+ } else if (state_len) {
+ PackedByteArray pba = state_variants[0];
+ memcpy(&ptr[ofs], pba.ptr(), state_len);
+ }
+
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer_id);
+ network_peer->set_transfer_channel(0);
+ network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ return network_peer->put_packet(ptr, ofs + state_len);
+}
+
+void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) {
+ ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received");
+ int ofs = SPAWN_CMD_OFFSET;
+ uint32_t node_target = decode_uint32(&p_packet[ofs]);
+ Node *parent = multiplayer->get_cached_node(p_from, node_target);
+ ofs += 4;
+ ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found.");
+
+ uint32_t name_len = decode_uint32(&p_packet[ofs]);
+ ofs += 4;
+ ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len));
+ ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size.");
+
+ const String name = String::utf8((const char *)&p_packet[ofs], name_len);
+ // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names.
+ ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name));
+ ofs += name_len;
+
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) {
+ String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id);
+ if (p_spawn) {
+ const bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1;
+
+ ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name));
+ RES res = ResourceLoader::load(scene_path);
+ ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path);
+ PackedScene *scene = Object::cast_to<PackedScene>(res.ptr());
+ ERR_FAIL_COND(!scene);
+ Node *node = scene->instantiate();
+ ERR_FAIL_COND(!node);
+ replicated_nodes[node->get_instance_id()] = p_scene_id;
+ int size;
+ _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw);
+ parent->_add_child_nocheck(node, name);
+ emit_signal(SNAME("spawned"), p_scene_id, node);
+ } else {
+ ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name));
+ Node *node = parent->get_node(name);
+ ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name));
+ emit_signal(SNAME("despawned"), p_scene_id, node);
+ replicated_nodes.erase(node->get_instance_id());
+ node->queue_delete();
+ }
+ } else {
+ PackedByteArray data;
+ if (p_packet_len > ofs) {
+ data.resize(p_packet_len - ofs);
+ memcpy(data.ptrw(), &p_packet[ofs], data.size());
+ }
+ if (p_spawn) {
+ emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data);
+ } else {
+ emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data);
+ }
+ }
+}
+
+void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) {
+ ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received");
+ ResourceUID::ID id = decode_uint64(&p_packet[1]);
+ ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id));
+
+ const SceneConfig &cfg = replications[id];
+ if (cfg.on_spawn_despawn_receive.is_valid()) {
+ int ofs = SPAWN_CMD_OFFSET;
+ bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1;
+ Variant data;
+ int left = p_packet_len - ofs;
+ if (is_raw && left) {
+ PackedByteArray pba;
+ pba.resize(left);
+ memcpy(pba.ptrw(), &p_packet[ofs], pba.size());
+ data = pba;
+ } else if (left) {
+ ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK);
+ }
+
+ Variant args[4];
+ args[0] = p_from;
+ args[1] = id;
+ args[2] = data;
+ args[3] = p_spawn;
+ const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce);
+ ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed");
+ } else {
+ _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn);
+ }
+}
+
+Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) {
+ ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object");
+ for (const StringName &prop : p_properties) {
+ bool valid = false;
+ const Variant v = p_obj->get(prop, &valid);
+ ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop));
+ r_variant.push_back(v);
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::_encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) {
+ r_len = 0;
+ int size = 0;
+
+ // Try raw encoding optimization.
+ if (r_raw && p_variants.size() == 1) {
+ *r_raw = false;
+ const Variant v = p_variants[0];
+ if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
+ *r_raw = true;
+ const PackedByteArray pba = v;
+ if (p_buffer) {
+ memcpy(p_buffer, pba.ptr(), pba.size());
+ }
+ r_len += pba.size();
+ } else {
+ multiplayer->encode_and_compress_variant(v, p_buffer, size);
+ r_len += size;
+ }
+ return OK;
+ }
+
+ // Regular encoding.
+ for (const Variant &v : p_variants) {
+ multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size);
+ r_len += size;
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::_decode_state(const List<StringName> &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) {
+ r_len = 0;
+ int argc = p_properties.size();
+ if (argc == 0 && p_raw) {
+ ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes.");
+ return OK;
+ }
+ ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
+ if (p_raw) {
+ r_len = p_len;
+ PackedByteArray pba;
+ pba.resize(p_len);
+ memcpy(pba.ptrw(), p_buffer, p_len);
+ p_obj->set(p_properties[0], pba);
+ return OK;
+ }
+
+ Vector<Variant> args;
+ Vector<const Variant *> argp;
+ args.resize(argc);
+
+ for (int i = 0; i < argc; i++) {
+ ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
+
+ int vlen;
+ Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
+ r_len += vlen;
+ }
+ ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes.");
+
+ int i = 0;
+ for (const StringName &prop : p_properties) {
+ p_obj->set(prop, args[i]);
+ i += 1;
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) {
+ ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty");
+#ifdef TOOLS_ENABLED
+ if (!p_on_send.is_valid()) {
+ // We allow non scene spawning with custom callables.
+ String path = ResourceUID::get_singleton()->get_id_path(p_id);
+ RES res = ResourceLoader::load(path);
+ ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER);
+ }
+#endif
+ if (p_mode == REPLICATION_MODE_NONE) {
+ if (replications.has(p_id)) {
+ replications.erase(p_id);
+ }
+ } else {
+ SceneConfig cfg;
+ cfg.mode = p_mode;
+ for (int i = 0; i < p_props.size(); i++) {
+ cfg.properties.push_back(StringName(p_props[i]));
+ }
+ cfg.on_spawn_despawn_send = p_on_send;
+ cfg.on_spawn_despawn_receive = p_on_recv;
+ replications[p_id] = cfg;
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) {
+ int data_size = 0;
+ int is_raw = false;
+ if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) {
+ const PackedByteArray pba = p_data;
+ is_raw = true;
+ data_size = p_data.operator PackedByteArray().size();
+ } else if (p_data.get_type() == Variant::NIL) {
+ is_raw = true;
+ } else {
+ Error err = encode_variant(p_data, nullptr, data_size);
+ ERR_FAIL_COND_V(err, err);
+ }
+ MAKE_ROOM(SPAWN_CMD_OFFSET + data_size);
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT);
+ encode_uint64(p_scene_id, &ptr[1]);
+ if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) {
+ const PackedByteArray pba = p_data;
+ memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size());
+ } else if (data_size) {
+ encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size);
+ }
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer_id);
+ network_peer->set_transfer_channel(0);
+ network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size);
+}
+
+Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.on_spawn_despawn_send.is_valid()) {
+ return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true);
+ } else {
+ ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests.");
+ NodePath path = p_path;
+ Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr;
+ if (path.is_empty() && obj) {
+ Node *node = Object::cast_to<Node>(obj);
+ if (node && node->is_inside_tree()) {
+ path = node->get_path();
+ }
+ }
+ ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree");
+ return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false);
+ }
+}
+
+Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.on_spawn_despawn_send.is_valid()) {
+ return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false);
+ } else {
+ ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests.");
+ NodePath path = p_path;
+ Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr;
+ ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object.");
+ if (path.is_empty()) {
+ Node *node = Object::cast_to<Node>(obj);
+ if (node && node->is_inside_tree()) {
+ path = node->get_path();
+ }
+ }
+ ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree");
+ return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true);
+ }
+}
+
+Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) {
+ ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
+
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.on_spawn_despawn_send.is_valid()) {
+ Variant args[4];
+ args[0] = p_peer;
+ args[1] = p_scene_id;
+ args[2] = p_obj;
+ args[3] = true;
+ const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_spawn_despawn_send.call(argp, 4, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed");
+ return OK;
+ } else {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation");
+ return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn);
+ }
+}
+
+Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) {
+ return _spawn_despawn(p_scene_id, p_obj, p_peer, true);
+}
+
+Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) {
+ return _spawn_despawn(p_scene_id, p_obj, p_peer, false);
+}
+
+PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj) {
+ PackedByteArray state;
+ ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ int len = 0;
+ List<Variant> state_vars;
+ Error err = _get_state(cfg.properties, p_obj, state_vars);
+ ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state.");
+ err = _encode_state(state_vars, nullptr, len);
+ ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state.");
+ state.resize(len);
+ _encode_state(state_vars, state.ptrw(), len);
+ return state;
+}
+
+Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) {
+ ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ int size;
+ return _decode_state(cfg.properties, p_obj, p_data.ptr(), p_data.size(), size);
+}
+
+void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
+ if (!multiplayer->has_network_peer()) {
+ return;
+ }
+ Node *root_node = multiplayer->get_root_node();
+ ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node);
+ NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path());
+ if (path.is_empty()) {
+ return;
+ }
+ ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene);
+ if (!replications.has(id)) {
+ return;
+ }
+ const SceneConfig &cfg = replications[id];
+ if (p_enter) {
+ if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) {
+ replicated_nodes[p_node->get_instance_id()] = id;
+ spawn(id, p_node, 0);
+ }
+ emit_signal(SNAME("replicated_instance_added"), id, p_node);
+ } else {
+ if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) {
+ replicated_nodes.erase(p_node->get_instance_id());
+ despawn(id, p_node, 0);
+ }
+ emit_signal(SNAME("replicated_instance_removed"), id, p_node);
+ }
+}
+
+void MultiplayerReplicator::spawn_all(int p_peer) {
+ for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) {
+ // Only server mode adds to replicated_nodes, no need to check it.
+ Object *obj = ObjectDB::get_instance(E.key);
+ ERR_CONTINUE(!obj);
+ Node *node = Object::cast_to<Node>(obj);
+ ERR_CONTINUE(!node);
+ spawn(E.value, node, p_peer);
+ }
+}
+
+void MultiplayerReplicator::clear() {
+ replicated_nodes.clear();
+}
+
+void MultiplayerReplicator::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
+ ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath()));
+ ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath()));
+ ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state);
+ ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state);
+
+ ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+ ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+ ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+ ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE);
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER);
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM);
+}
diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h
new file mode 100644
index 0000000000..e19dd80602
--- /dev/null
+++ b/core/io/multiplayer_replicator.h
@@ -0,0 +1,99 @@
+/*************************************************************************/
+/* multiplayer_replicator.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 MULTIPLAYER_REPLICATOR_H
+#define MULTIPLAYER_REPLICATOR_H
+
+#include "core/io/multiplayer_api.h"
+#include "core/variant/typed_array.h"
+
+class MultiplayerReplicator : public Object {
+ GDCLASS(MultiplayerReplicator, Object);
+
+public:
+ enum {
+ SPAWN_CMD_OFFSET = 9,
+ };
+
+ enum ReplicationMode {
+ REPLICATION_MODE_NONE,
+ REPLICATION_MODE_SERVER,
+ REPLICATION_MODE_CUSTOM,
+ };
+
+ struct SceneConfig {
+ ReplicationMode mode;
+ List<StringName> properties;
+ Callable on_spawn_despawn_send;
+ Callable on_spawn_despawn_receive;
+ };
+
+protected:
+ static void _bind_methods();
+
+private:
+ MultiplayerAPI *multiplayer = nullptr;
+ Vector<uint8_t> packet_cache;
+ Map<ResourceUID::ID, SceneConfig> replications;
+ Map<ObjectID, ResourceUID::ID> replicated_nodes;
+
+ Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr);
+ Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false);
+ Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant);
+ Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn);
+ Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn);
+ void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
+ Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn);
+
+public:
+ void clear();
+
+ Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
+ Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
+ Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
+
+ Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
+ Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
+ PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node);
+ Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data);
+
+ // Used by MultiplayerAPI
+ void spawn_all(int p_peer);
+ void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
+ void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
+
+ MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
+ multiplayer = p_multiplayer;
+ }
+};
+
+VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode);
+
+#endif // MULTIPLAYER_REPLICATOR_H
diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py
index 2c6b8cddc9..9948620c73 100644
--- a/core/object/make_virtuals.py
+++ b/core/object/make_virtuals.py
@@ -1,8 +1,8 @@
proto = """
#define GDVIRTUAL$VER($RET m_name $ARG) \\
-GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\
StringName _gdvirtual_##m_name##_sn = #m_name;\\
bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
+ GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\
ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\
if (script_instance) {\\
Callable::CallError ce; \\
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index adf068eb2f..b7d2bac96d 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -405,11 +405,11 @@ String UndoRedo::get_current_action_name() const {
return actions[current_action].name;
}
-bool UndoRedo::has_undo() {
+bool UndoRedo::has_undo() const {
return current_action >= 0;
}
-bool UndoRedo::has_redo() {
+bool UndoRedo::has_redo() const {
return (current_action + 1) < actions.size();
}
diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h
index 8f009830e3..d1ce252d86 100644
--- a/core/object/undo_redo.h
+++ b/core/object/undo_redo.h
@@ -121,8 +121,8 @@ public:
String get_action_name(int p_id);
void clear_history(bool p_increase_version = true);
- bool has_undo();
- bool has_redo();
+ bool has_undo() const;
+ bool has_redo() const;
uint64_t get_version() const;
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 76a6da51e1..63390919f4 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -277,18 +277,13 @@ String OS::get_user_data_dir() const {
return ".";
}
-// Android OS path to app's external data storage
-String OS::get_external_data_dir() const {
- return get_user_data_dir();
-};
-
// Absolute path to res://
String OS::get_resource_dir() const {
return ProjectSettings::get_singleton()->get_resource_path();
}
// Access system-specific dirs like Documents, Downloads, etc.
-String OS::get_system_dir(SystemDir p_dir) const {
+String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return ".";
}
diff --git a/core/os/os.h b/core/os/os.h
index 0466d94acd..55b21266fc 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -250,7 +250,6 @@ public:
virtual String get_bundle_resource_dir() const;
virtual String get_user_data_dir() const;
- virtual String get_external_data_dir() const;
virtual String get_resource_dir() const;
enum SystemDir {
@@ -264,7 +263,7 @@ public:
SYSTEM_DIR_RINGTONES,
};
- virtual String get_system_dir(SystemDir p_dir) const;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
virtual Error move_to_trash(const String &p_path) { return FAILED; }
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index f801be3db3..4fabd1551e 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -49,6 +49,7 @@
#include "core/io/marshalls.h"
#include "core/io/multiplayer_api.h"
#include "core/io/multiplayer_peer.h"
+#include "core/io/multiplayer_replicator.h"
#include "core/io/packed_data_container.h"
#include "core/io/packet_peer.h"
#include "core/io/packet_peer_dtls.h"
@@ -193,6 +194,7 @@ void register_core_types() {
ResourceLoader::add_resource_format_loader(resource_format_loader_crypto);
GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer);
+ GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator);
GDREGISTER_CLASS(MultiplayerAPI);
GDREGISTER_CLASS(MainLoop);
GDREGISTER_CLASS(Translation);
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 2fdf6e84e5..d2d563c5dc 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1396,7 +1396,13 @@ String String::num(double p_num, int p_decimals) {
#ifndef NO_USE_STDLIB
if (p_decimals < 0) {
- p_decimals = 14 - (int)floor(log10(p_num));
+ p_decimals = 14;
+ const double abs_num = ABS(p_num);
+ if (abs_num > 10) {
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ p_decimals -= (int)floor(log10(abs_num));
+ }
}
if (p_decimals > MAX_DECIMALS) {
p_decimals = MAX_DECIMALS;
@@ -1625,24 +1631,31 @@ String String::num_real(double p_num, bool p_trailing) {
String s;
String sd;
- /* integer part */
+
+ // Integer part.
bool neg = p_num < 0;
p_num = ABS(p_num);
int intn = (int)p_num;
- /* decimal part */
+ // Decimal part.
- if ((int)p_num != p_num) {
- double dec = p_num - (double)((int)p_num);
+ if (intn != p_num) {
+ double dec = p_num - (double)(intn);
int digit = 0;
#if REAL_T_IS_DOUBLE
- int decimals = 14 - (int)floor(log10(p_num));
+ int decimals = 14;
#else
- int decimals = 6 - (int)floor(log10(p_num));
+ int decimals = 6;
#endif
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ if (p_num > 10) {
+ decimals -= (int)floor(log10(p_num));
+ }
+
if (decimals > MAX_DECIMALS) {
decimals = MAX_DECIMALS;
}
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index 50c48fd386..a214a238a6 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -1423,7 +1423,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, itos(p_variant.operator int64_t()));
} break;
case Variant::FLOAT: {
- String s = rtosfix(p_variant.operator real_t());
+ String s = rtosfix(p_variant.operator double());
if (s != "inf" && s != "nan") {
if (s.find(".") == -1 && s.find("e") == -1) {
s += ".0";
diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml
index 919960cc9f..e8e5a286b4 100644
--- a/doc/classes/Directory.xml
+++ b/doc/classes/Directory.xml
@@ -187,7 +187,7 @@
<argument index="0" name="from" type="String" />
<argument index="1" name="to" type="String" />
<description>
- Renames (move) the [code]from[/code] file to the [code]to[/code] 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.
+ Renames (move) the [code]from[/code] file or directory to the [code]to[/code] destination. Both arguments should be paths to files or directories, either relative or absolute. If the destination file or directory exists and is not access-protected, it will be overwritten.
Returns one of the [enum Error] code constants ([code]OK[/code] on success).
</description>
</method>
diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml
index b9f50ad02a..610b00efe9 100644
--- a/doc/classes/MultiplayerAPI.xml
+++ b/doc/classes/MultiplayerAPI.xml
@@ -66,34 +66,6 @@
Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers.
</description>
</method>
- <method name="send_despawn">
- <return type="int" enum="Error" />
- <argument index="0" name="peer_id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="path" type="NodePath" />
- <argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" />
- <description>
- Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal network_despawn]. In all other cases no deletion happens, and the signal [signal network_despawn_request] is emitted instead.
- </description>
- </method>
- <method name="send_spawn">
- <return type="int" enum="Error" />
- <argument index="0" name="peer_id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="path" type="NodePath" />
- <argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" />
- <description>
- Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal network_spawn]. In all other cases no instantiation happens, and the signal [signal network_spawn_request] is emitted instead.
- </description>
- </method>
- <method name="spawnable_config">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="spawn_mode" type="int" enum="MultiplayerAPI.SpawnMode" />
- <description>
- Configures the MultiplayerAPI to track instances of the [PackedScene] idenfied by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. See [enum SpawnMode] for the possible configurations.
- </description>
- </method>
</methods>
<members>
<member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false">
@@ -106,6 +78,8 @@
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections.
</member>
+ <member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator">
+ </member>
<member name="root_node" type="Node" setter="set_root_node" getter="get_root_node">
The root node to use for RPCs. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed.
This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene.
@@ -122,25 +96,6 @@
Emitted when this MultiplayerAPI's [member network_peer] fails to establish a connection to a server. Only emitted on clients.
</description>
</signal>
- <signal name="network_despawn">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="node" type="Node" />
- <argument index="3" name="data" type="PackedByteArray" />
- <description>
- Emitted on a client before deleting a local Node upon receiving a despawn request from the server.
- </description>
- </signal>
- <signal name="network_despawn_request">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="parent" type="Node" />
- <argument index="3" name="name" type="String" />
- <argument index="4" name="data" type="PackedByteArray" />
- <description>
- Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM].
- </description>
- </signal>
<signal name="network_peer_connected">
<argument index="0" name="id" type="int" />
<description>
@@ -160,39 +115,6 @@
Emitted when this MultiplayerAPI's [member network_peer] receive a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
</description>
</signal>
- <signal name="network_spawn">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="node" type="Node" />
- <argument index="3" name="data" type="PackedByteArray" />
- <description>
- Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server.
- </description>
- </signal>
- <signal name="network_spawn_request">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="parent" type="Node" />
- <argument index="3" name="name" type="String" />
- <argument index="4" name="data" type="PackedByteArray" />
- <description>
- Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM].
- </description>
- </signal>
- <signal name="network_spawnable_added">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawnable_config].
- </description>
- </signal>
- <signal name="network_spawnable_removed">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawnable_config].
- </description>
- </signal>
<signal name="server_disconnected">
<description>
Emitted when this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients.
@@ -212,14 +134,5 @@
<constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode">
Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master].
</constant>
- <constant name="SPAWN_MODE_NONE" value="0" enum="SpawnMode">
- Used with [method spawnable_config] to identify a [PackedScene] that should not be replicated.
- </constant>
- <constant name="SPAWN_MODE_SERVER" value="1" enum="SpawnMode">
- Used with [method spawnable_config] to identify a [PackedScene] that should be automatically replicated from server to clients.
- </constant>
- <constant name="SPAWN_MODE_CUSTOM" value="2" enum="SpawnMode">
- Used with [method spawnable_config] to identify a [PackedScene] that can be manually replicated among peers.
- </constant>
</constants>
</class>
diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml
new file mode 100644
index 0000000000..15029e181f
--- /dev/null
+++ b/doc/classes/MultiplayerReplicator.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="MultiplayerReplicator" inherits="Object" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="decode_state">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <argument index="2" name="data" type="PackedByteArray" />
+ <description>
+ Decode the given [code]data[/code] representing a spawnable state into [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when a client receives a server spawn for a scene with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
+ Tip: You may find this function useful in servers when parsing spawn requests from clients, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
+ </description>
+ </method>
+ <method name="despawn">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <argument index="2" name="peer_id" type="int" default="0" />
+ <description>
+ </description>
+ </method>
+ <method name="encode_state">
+ <return type="PackedByteArray" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <description>
+ Encode the given [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when the server spawns scenes with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
+ Tip: You may find this function useful when requesting spawns from clients to server, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
+ </description>
+ </method>
+ <method name="send_despawn">
+ <return type="int" enum="Error" />
+ <argument index="0" name="peer_id" type="int" />
+ <argument index="1" name="scene_id" type="int" />
+ <argument index="2" name="data" type="Variant" default="null" />
+ <argument index="3" name="path" type="NodePath" default="NodePath(&quot;&quot;)" />
+ <description>
+ Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead.
+ </description>
+ </method>
+ <method name="send_spawn">
+ <return type="int" enum="Error" />
+ <argument index="0" name="peer_id" type="int" />
+ <argument index="1" name="scene_id" type="int" />
+ <argument index="2" name="data" type="Variant" default="null" />
+ <argument index="3" name="path" type="NodePath" default="NodePath(&quot;&quot;)" />
+ <description>
+ Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead.
+ </description>
+ </method>
+ <method name="spawn">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <argument index="2" name="peer_id" type="int" default="0" />
+ <description>
+ </description>
+ </method>
+ <method name="spawn_config">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="spawn_mode" type="int" enum="MultiplayerReplicator.ReplicationMode" />
+ <argument index="2" name="properties" type="StringName[]" default="[]" />
+ <argument index="3" name="custom_send" type="Callable" />
+ <argument index="4" name="custom_receive" type="Callable" />
+ <description>
+ Configures the MultiplayerAPI to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn.
+ Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
+ </description>
+ </method>
+ </methods>
+ <signals>
+ <signal name="despawn_requested">
+ <argument index="0" name="id" type="int" />
+ <argument index="1" name="scene_id" type="int" />
+ <argument index="2" name="parent" type="Node" />
+ <argument index="3" name="name" type="String" />
+ <argument index="4" name="data" type="PackedByteArray" />
+ <description>
+ Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
+ </description>
+ </signal>
+ <signal name="despawned">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ Emitted on a client before deleting a local Node upon receiving a despawn request from the server.
+ </description>
+ </signal>
+ <signal name="replicated_instance_added">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawn_config].
+ </description>
+ </signal>
+ <signal name="replicated_instance_removed">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawn_config].
+ </description>
+ </signal>
+ <signal name="spawn_requested">
+ <argument index="0" name="id" type="int" />
+ <argument index="1" name="scene_id" type="int" />
+ <argument index="2" name="parent" type="Node" />
+ <argument index="3" name="name" type="String" />
+ <argument index="4" name="data" type="PackedByteArray" />
+ <description>
+ Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
+ </description>
+ </signal>
+ <signal name="spawned">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server.
+ </description>
+ </signal>
+ </signals>
+ <constants>
+ <constant name="REPLICATION_MODE_NONE" value="0" enum="ReplicationMode">
+ Used with [method spawn_config] to identify a [PackedScene] that should not be replicated.
+ </constant>
+ <constant name="REPLICATION_MODE_SERVER" value="1" enum="ReplicationMode">
+ Used with [method spawn_config] to identify a [PackedScene] that should be automatically replicated from server to clients.
+ </constant>
+ <constant name="REPLICATION_MODE_CUSTOM" value="2" enum="ReplicationMode">
+ Used with [method spawn_config] to identify a [PackedScene] that can be manually replicated among peers.
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index e5d45189c7..757730f6c8 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -197,12 +197,6 @@
Returns the path to the current engine executable.
</description>
</method>
- <method name="get_external_data_dir" qualifiers="const">
- <return type="String" />
- <description>
- On Android, returns the absolute directory path where user data can be written to external storage if available. On all other platforms, this will return the same location as [method get_user_data_dir].
- </description>
- </method>
<method name="get_granted_permissions" qualifiers="const">
<return type="PackedStringArray" />
<description>
@@ -270,9 +264,11 @@
<method name="get_system_dir" qualifiers="const">
<return type="String" />
<argument index="0" name="dir" type="int" enum="OS.SystemDir" />
+ <argument index="1" name="shared_storage" type="bool" default="true" />
<description>
Returns the actual path to commonly used folders across different platforms. Available locations are specified in [enum SystemDir].
[b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows.
+ [b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android.
</description>
</method>
<method name="get_thread_caller_id" qualifiers="const">
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index c329821646..24f7f4274e 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -448,6 +448,7 @@
<method name="shader_compile_binary_from_spirv">
<return type="PackedByteArray" />
<argument index="0" name="spirv_data" type="RDShaderSPIRV" />
+ <argument index="1" name="name" type="String" default="&quot;&quot;" />
<description>
</description>
</method>
@@ -465,8 +466,9 @@
</description>
</method>
<method name="shader_create_from_spirv">
- <return type="PackedByteArray" />
+ <return type="RID" />
<argument index="0" name="spirv_data" type="RDShaderSPIRV" />
+ <argument index="1" name="name" type="String" default="&quot;&quot;" />
<description>
</description>
</method>
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 0376a3f96e..467e2f901f 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -410,6 +410,21 @@
<argument index="0" name="number" type="float" />
<argument index="1" name="decimals" type="int" default="-1" />
<description>
+ Converts a [float] to a string representation of a decimal number.
+ The number of decimal places can be specified with [code]decimals[/code]. If [code]decimals[/code] is [code]-1[/code] (default), decimal places will be automatically adjusted so that the string representation has 14 significant digits (counting both digits to the left and the right of the decimal point).
+ Trailing zeros are not included in the string. The last digit will be rounded and not truncated.
+ Some examples:
+ [codeblock]
+ String.num(3.141593) # "3.141593"
+ String.num(3.141593, 3) # "3.142"
+ String.num(3.14159300) # "3.141593", no trailing zeros.
+ # Last digit will be rounded up here, which reduces total digit count since
+ # trailing zeros are removed:
+ String.num(42.129999, 5) # "42.13"
+ # If `decimals` is not specified, the total amount of significant digits is 14:
+ String.num(-0.0000012345432123454321) # "-0.00000123454321"
+ String.num(-10000.0000012345432123454321) # "-10000.0000012345"
+ [/codeblock]
</description>
</method>
<method name="num_scientific" qualifiers="static">
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index 68d20e41d0..7aa627b7d0 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -464,12 +464,24 @@
Returns if the user has IME text.
</description>
</method>
+ <method name="has_redo" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if a "redo" action is available.
+ </description>
+ </method>
<method name="has_selection" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the user has selected text.
</description>
</method>
+ <method name="has_undo" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if an "undo" action is available.
+ </description>
+ </method>
<method name="insert_line_at">
<return type="void" />
<argument index="0" name="line" type="int" />
diff --git a/doc/classes/Theme.xml b/doc/classes/Theme.xml
index 1124fb8084..edf5874432 100644
--- a/doc/classes/Theme.xml
+++ b/doc/classes/Theme.xml
@@ -81,19 +81,6 @@
Unmarks [code]theme_type[/code] as being a variation of any other type.
</description>
</method>
- <method name="copy_default_theme">
- <return type="void" />
- <description>
- Sets the theme's values to a copy of the default theme values.
- </description>
- </method>
- <method name="copy_theme">
- <return type="void" />
- <argument index="0" name="other" type="Theme" />
- <description>
- Sets the theme's values to a copy of a given theme.
- </description>
- </method>
<method name="get_color" qualifiers="const">
<return type="Color" />
<argument index="0" name="name" type="StringName" />
@@ -340,6 +327,14 @@
Returns [code]true[/code] if [code]theme_type[/code] is marked as a variation of [code]base_type[/code] in this theme.
</description>
</method>
+ <method name="merge_with">
+ <return type="void" />
+ <argument index="0" name="other" type="Theme" />
+ <description>
+ Adds missing and overrides existing definitions with values from the [code]other[/code] [Theme].
+ [b]Note:[/b] This modifies the current theme. If you want to merge two themes together without modifying either one, create a new empty theme and merge the other two into it one after another.
+ </description>
+ </method>
<method name="rename_color">
<return type="void" />
<argument index="0" name="old_name" type="StringName" />
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 50a573d30f..ed24905254 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -552,6 +552,9 @@
<theme_item name="hseparation" data_type="constant" type="int" default="4">
The horizontal space between item cells. This is also used as the margin at the start of an item when folding is disabled.
</theme_item>
+ <theme_item name="indeterminate" data_type="icon" type="Texture2D">
+ The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate.
+ </theme_item>
<theme_item name="item_margin" data_type="constant" type="int" default="12">
The horizontal margin at the start of an item. This is used when folding is enabled for the item.
</theme_item>
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 944b9fdc06..406c074758 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -340,6 +340,13 @@
Returns [code]true[/code] if column [code]column[/code] is editable.
</description>
</method>
+ <method name="is_indeterminate" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="column" type="int" />
+ <description>
+ Returns [code]true[/code] if the given column is indeterminate.
+ </description>
+ </method>
<method name="is_selectable" qualifiers="const">
<return type="bool" />
<argument index="0" name="column" type="int" />
@@ -415,7 +422,7 @@
<argument index="0" name="column" type="int" />
<argument index="1" name="checked" type="bool" />
<description>
- If [code]true[/code], the column [code]column[/code] is checked.
+ If [code]true[/code], the column [code]column[/code] is checked. Clears column's indeterminate status.
</description>
</method>
<method name="set_custom_as_button">
@@ -507,6 +514,15 @@
Sets the given column's icon's texture region.
</description>
</method>
+ <method name="set_indeterminate">
+ <return type="void" />
+ <argument index="0" name="column" type="int" />
+ <argument index="1" name="indeterminate" type="bool" />
+ <description>
+ If [code]true[/code], the column [code]column[/code] is marked indeterminate.
+ [b]Note:[/b] If set [code]true[/code] from [code]false[/code], then column is cleared of checked status.
+ </description>
+ </method>
<method name="set_language">
<return type="void" />
<argument index="0" name="column" type="int" />
diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml
index 4417447891..def6fe5d1f 100644
--- a/doc/classes/UndoRedo.xml
+++ b/doc/classes/UndoRedo.xml
@@ -166,13 +166,13 @@
This is useful mostly to check if something changed from a saved version.
</description>
</method>
- <method name="has_redo">
+ <method name="has_redo" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if a "redo" action is available.
</description>
</method>
- <method name="has_undo">
+ <method name="has_undo" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if an "undo" action is available.
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index dbe932f74c..63510d261a 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -4442,7 +4442,10 @@ bool RenderingDeviceVulkan::_uniform_add_binding(Vector<Vector<VkDescriptorSetLa
}
#endif
-#define SHADER_BINARY_VERSION 1
+//version 1: initial
+//version 2: Added shader name
+
+#define SHADER_BINARY_VERSION 2
String RenderingDeviceVulkan::shader_get_binary_cache_key() const {
return "Vulkan-SV" + itos(SHADER_BINARY_VERSION);
@@ -4476,9 +4479,10 @@ struct RenderingDeviceVulkanShaderBinaryData {
uint32_t push_constant_size;
uint32_t push_constants_vk_stage;
uint32_t stage_count;
+ uint32_t shader_name_len;
};
-Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) {
+Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name) {
RenderingDeviceVulkanShaderBinaryData binary_data;
binary_data.vertex_input_mask = 0;
binary_data.fragment_outputs = 0;
@@ -4835,9 +4839,19 @@ Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Ve
binary_data.set_count = uniform_info.size();
binary_data.stage_count = p_spirv.size();
+ CharString shader_name_utf = p_shader_name.utf8();
+
+ binary_data.shader_name_len = shader_name_utf.length();
+
uint32_t total_size = sizeof(uint32_t) * 3; //header + version + main datasize;
total_size += sizeof(RenderingDeviceVulkanShaderBinaryData);
+ total_size += binary_data.shader_name_len;
+
+ if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange
+ total_size += 4 - (binary_data.shader_name_len % 4);
+ }
+
for (int i = 0; i < uniform_info.size(); i++) {
total_size += sizeof(uint32_t);
total_size += uniform_info[i].size() * sizeof(RenderingDeviceVulkanShaderBinaryDataBinding);
@@ -4864,6 +4878,12 @@ Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Ve
offset += sizeof(uint32_t);
memcpy(binptr + offset, &binary_data, sizeof(RenderingDeviceVulkanShaderBinaryData));
offset += sizeof(RenderingDeviceVulkanShaderBinaryData);
+ memcpy(binptr + offset, shader_name_utf.ptr(), binary_data.shader_name_len);
+ offset += binary_data.shader_name_len;
+
+ if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange
+ offset += 4 - (binary_data.shader_name_len % 4);
+ }
for (int i = 0; i < uniform_info.size(); i++) {
int count = uniform_info[i].size();
@@ -4934,6 +4954,16 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_
read_offset += sizeof(uint32_t) * 3 + bin_data_size;
+ String name;
+
+ if (binary_data.shader_name_len) {
+ name.parse_utf8((const char *)(binptr + read_offset), binary_data.shader_name_len);
+ read_offset += binary_data.shader_name_len;
+ if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange
+ read_offset += 4 - (binary_data.shader_name_len % 4);
+ }
+ }
+
Vector<Vector<VkDescriptorSetLayoutBinding>> set_bindings;
Vector<Vector<UniformInfo>> uniform_info;
@@ -5088,6 +5118,7 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_
shader.compute_local_size[1] = compute_local_size[1];
shader.compute_local_size[2] = compute_local_size[2];
shader.specialization_constants = specialization_constants;
+ shader.name = name;
String error_text;
@@ -6449,7 +6480,6 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
specialization_info.write[i].pData = data_ptr;
specialization_info.write[i].mapEntryCount = specialization_map_entries[i].size();
specialization_info.write[i].pMapEntries = specialization_map_entries[i].ptr();
-
pipeline_stages.write[i].pSpecializationInfo = specialization_info.ptr() + i;
}
}
@@ -6476,7 +6506,7 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
RenderPipeline pipeline;
VkResult err = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &graphics_pipeline_create_info, nullptr, &pipeline.pipeline);
- ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + ".");
+ ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + " for shader '" + shader->name + "'.");
pipeline.set_formats = shader->set_formats;
pipeline.push_constant_stages = shader->push_constant.push_constants_vk_stage;
diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h
index 6175369285..5ee2ca07f2 100644
--- a/drivers/vulkan/rendering_device_vulkan.h
+++ b/drivers/vulkan/rendering_device_vulkan.h
@@ -639,6 +639,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
Vector<VkPipelineShaderStageCreateInfo> pipeline_stages;
Vector<SpecializationConstant> specialization_constants;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
+ String name; //used for debug
};
String _shader_uniform_debug(RID p_shader, int p_set = -1);
@@ -1088,7 +1089,7 @@ public:
/****************/
virtual String shader_get_binary_cache_key() const;
- virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv);
+ virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = "");
virtual RID shader_create_from_bytecode(const Vector<uint8_t> &p_shader_binary);
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index 87749450c4..625c222b67 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -976,37 +976,35 @@ Error VulkanContext::_create_device() {
sdevice.queueCreateInfoCount = 2;
}
-#ifdef VK_VERSION_1_2
VkPhysicalDeviceVulkan11Features vulkan11features;
-
- vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
- vulkan11features.pNext = nullptr;
- // !BAS! Need to figure out which ones of these we want enabled...
- vulkan11features.storageBuffer16BitAccess = 0;
- vulkan11features.uniformAndStorageBuffer16BitAccess = 0;
- vulkan11features.storagePushConstant16 = 0;
- vulkan11features.storageInputOutput16 = 0;
- vulkan11features.multiview = multiview_capabilities.is_supported;
- vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported;
- vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported;
- vulkan11features.variablePointersStorageBuffer = 0;
- vulkan11features.variablePointers = 0;
- vulkan11features.protectedMemory = 0;
- vulkan11features.samplerYcbcrConversion = 0;
- vulkan11features.shaderDrawParameters = 0;
-
- sdevice.pNext = &vulkan11features;
-#elif VK_VERSION_1_1
VkPhysicalDeviceMultiviewFeatures multiview_features;
+ if (vulkan_major > 1 || vulkan_minor >= 2) {
+ vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
+ vulkan11features.pNext = nullptr;
+ // !BAS! Need to figure out which ones of these we want enabled...
+ vulkan11features.storageBuffer16BitAccess = 0;
+ vulkan11features.uniformAndStorageBuffer16BitAccess = 0;
+ vulkan11features.storagePushConstant16 = 0;
+ vulkan11features.storageInputOutput16 = 0;
+ vulkan11features.multiview = multiview_capabilities.is_supported;
+ vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported;
+ vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported;
+ vulkan11features.variablePointersStorageBuffer = 0;
+ vulkan11features.variablePointers = 0;
+ vulkan11features.protectedMemory = 0;
+ vulkan11features.samplerYcbcrConversion = 0;
+ vulkan11features.shaderDrawParameters = 0;
+
+ sdevice.pNext = &vulkan11features;
+ } else if (vulkan_major == 1 && vulkan_minor == 1) {
+ multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
+ multiview_features.pNext = nullptr;
+ multiview_features.multiview = multiview_capabilities.is_supported;
+ multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported;
+ multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported;
- multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
- multiview_features.pNext = nullptr;
- multiview_features.multiview = multiview_capabilities.is_supported;
- multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported;
- multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported;
-
- sdevice.pNext = &multiview_features;
-#endif
+ sdevice.pNext = &multiview_features;
+ }
err = vkCreateDevice(gpu, &sdevice, nullptr, &device);
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 48f2be450b..39095c42a4 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1429,13 +1429,13 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) {
void CodeTextEditor::goto_line(int p_line) {
text_editor->deselect();
text_editor->unfold_line(p_line);
- text_editor->call_deferred(SNAME("cursor_set_line"), p_line);
+ text_editor->call_deferred(SNAME("set_caret_line"), p_line);
}
void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
text_editor->unfold_line(p_line);
- text_editor->call_deferred(SNAME("cursor_set_line"), p_line);
- text_editor->call_deferred(SNAME("cursor_set_column"), p_begin);
+ text_editor->call_deferred(SNAME("set_caret_line"), p_line);
+ text_editor->call_deferred(SNAME("set_caret_column"), p_begin);
text_editor->select(p_line, p_begin, p_line, p_end);
}
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 4cd2e8bdd0..3adb7688b0 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -2592,26 +2592,26 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
case EDIT_UNDO: {
if (Input::get_singleton()->get_mouse_button_mask() & 0x7) {
- log->add_message("Can't undo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);
} else {
String action = editor_data.get_undo_redo().get_current_action_name();
if (!editor_data.get_undo_redo().undo()) {
- log->add_message("Nothing to undo.", EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(TTR("Nothing to undo."), EditorLog::MSG_TYPE_EDITOR);
} else if (action != "") {
- log->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
}
}
} break;
case EDIT_REDO: {
if (Input::get_singleton()->get_mouse_button_mask() & 0x7) {
- log->add_message("Can't redo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(TTR("Can't redo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);
} else {
if (!editor_data.get_undo_redo().redo()) {
- log->add_message("Nothing to redo.", EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(TTR("Nothing to redo."), EditorLog::MSG_TYPE_EDITOR);
} else {
String action = editor_data.get_undo_redo().get_current_action_name();
- log->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR);
+ log->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
}
}
} break;
@@ -3014,8 +3014,13 @@ void EditorNode::_update_file_menu_opened() {
close_scene_sc->set_name(TTR("Close Scene"));
Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene");
reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene"));
+
PopupMenu *pop = file_menu->get_popup();
pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty());
+
+ const UndoRedo &undo_redo = editor_data.get_undo_redo();
+ pop->set_item_disabled(pop->get_item_index(EDIT_UNDO), !undo_redo.has_undo());
+ pop->set_item_disabled(pop->get_item_index(EDIT_REDO), !undo_redo.has_redo());
}
void EditorNode::_update_file_menu_closed() {
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index a802afda0f..91f00deeaa 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -195,11 +195,11 @@ void EditorSpinSlider::_update_value_input_stylebox() {
if (!value_input) {
return;
}
+
// Add a left margin to the stylebox to make the number align with the Label
// when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's
// default margins.
- Ref<StyleBoxFlat> stylebox =
- EditorNode::get_singleton()->get_theme_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate();
+ Ref<StyleBox> stylebox = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate();
// EditorSpinSliders with a label have more space on the left, so add an
// higher margin to match the location where the text begins.
// The margin values below were determined by empirical testing.
@@ -210,188 +210,197 @@ void EditorSpinSlider::_update_value_input_stylebox() {
stylebox->set_default_margin(SIDE_LEFT, (get_label() != String() ? 23 : 16) * EDSCALE);
stylebox->set_default_margin(SIDE_RIGHT, 0);
}
+
value_input->add_theme_style_override("normal", stylebox);
}
-void EditorSpinSlider::_notification(int p_what) {
- if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_OUT ||
- p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN ||
- p_what == NOTIFICATION_EXIT_TREE) {
- if (grabbing_spinner) {
- grabber->hide();
- Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
- grabbing_spinner = false;
- grabbing_spinner_attempt = false;
- }
- }
- if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_THEME_CHANGED) {
- _update_value_input_stylebox();
- }
+void EditorSpinSlider::_draw_spin_slider() {
+ updown_offset = -1;
- if (p_what == NOTIFICATION_DRAW) {
- updown_offset = -1;
+ RID ci = get_canvas_item();
+ bool rtl = is_layout_rtl();
+ Vector2 size = get_size();
- RID ci = get_canvas_item();
- bool rtl = is_layout_rtl();
- Vector2 size = get_size();
+ Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"));
+ if (!flat) {
+ draw_style_box(sb, Rect2(Vector2(), size));
+ }
+ Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit"));
+ int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit"));
+ int sep_base = 4 * EDSCALE;
+ int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better
- Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"));
- if (!flat) {
- draw_style_box(sb, Rect2(Vector2(), size));
- }
- Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit"));
- int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit"));
- int sep_base = 4 * EDSCALE;
- int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better
+ int label_width = font->get_string_size(label, font_size).width;
+ int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
- int label_width = font->get_string_size(label, font_size).width;
- int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
+ Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
- Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
+ if (get_step() == 1) {
+ number_width -= updown->get_width();
+ }
- if (get_step() == 1) {
- number_width -= updown->get_width();
- }
+ String numstr = get_text_value();
- String numstr = get_text_value();
+ int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
- int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
+ Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit"));
+ Color lc;
+ if (use_custom_label_color) {
+ lc = custom_label_color;
+ } else {
+ lc = fc;
+ }
- Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit"));
- Color lc;
- if (use_custom_label_color) {
- lc = custom_label_color;
+ if (flat && label != String()) {
+ Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor"));
+ if (rtl) {
+ draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);
} else {
- lc = fc;
+ draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);
}
+ }
- if (flat && label != String()) {
- Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor"));
- if (rtl) {
- draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);
- } else {
- draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);
- }
- }
+ if (has_focus()) {
+ Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
+ draw_style_box(focus, Rect2(Vector2(), size));
+ }
+
+ if (rtl) {
+ draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5));
+ } else {
+ draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5));
+ }
- if (has_focus()) {
- Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
- draw_style_box(focus, Rect2(Vector2(), size));
+ int suffix_start = numstr.length();
+ RID num_rid = TS->create_shaped_text();
+ TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size);
+
+ float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep);
+ Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs);
+ const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid);
+ int v_size = visual.size();
+ const TextServer::Glyph *glyphs = visual.ptr();
+ for (int i = 0; i < v_size; i++) {
+ for (int j = 0; j < glyphs[i].repeat; j++) {
+ if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) {
+ Color color = fc;
+ if (glyphs[i].start >= suffix_start) {
+ color.a *= 0.4;
+ }
+ if (glyphs[i].font_rid != RID()) {
+ TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
+ } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
+ }
+ }
+ text_ofs.x += glyphs[i].advance;
}
+ }
+ TS->free(num_rid);
+ if (get_step() == 1) {
+ Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
+ int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
- draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5));
+ updown_offset = sb->get_margin(SIDE_LEFT);
} else {
- draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5));
+ updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
}
-
- int suffix_start = numstr.length();
- RID num_rid = TS->create_shaped_text();
- TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size);
-
- float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep);
- Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs);
- const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid);
- int v_size = visual.size();
- const TextServer::Glyph *glyphs = visual.ptr();
- for (int i = 0; i < v_size; i++) {
- for (int j = 0; j < glyphs[i].repeat; j++) {
- if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) {
- Color color = fc;
- if (glyphs[i].start >= suffix_start) {
- color.a *= 0.4;
- }
- if (glyphs[i].font_rid != RID()) {
- TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
- } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
- TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
- }
- }
- text_ofs.x += glyphs[i].advance;
- }
+ Color c(1, 1, 1);
+ if (hover_updown) {
+ c *= Color(1.2, 1.2, 1.2);
}
- TS->free(num_rid);
-
- if (get_step() == 1) {
- Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
- int updown_vofs = (size.height - updown2->get_height()) / 2;
- if (rtl) {
- updown_offset = sb->get_margin(SIDE_LEFT);
+ draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
+ if (grabber->is_visible()) {
+ grabber->hide();
+ }
+ } else if (!hide_slider) {
+ int grabber_w = 4 * EDSCALE;
+ int width = size.width - sb->get_minimum_size().width - grabber_w;
+ int ofs = sb->get_offset().x;
+ int svofs = (size.height + vofs) / 2 - 1;
+ Color c = fc;
+ c.a = 0.2;
+
+ draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
+ int gofs = get_as_ratio() * width;
+ c.a = 0.9;
+ Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE);
+ draw_rect(grabber_rect, c);
+
+ grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.position + grabber_rect.size * 0.5;
+
+ bool display_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();
} else {
- updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
- }
- Color c(1, 1, 1);
- if (hover_updown) {
- c *= Color(1.2, 1.2, 1.2);
- }
- draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
- if (grabber->is_visible()) {
grabber->hide();
}
- } else if (!hide_slider) {
- int grabber_w = 4 * EDSCALE;
- int width = size.width - sb->get_minimum_size().width - grabber_w;
- int ofs = sb->get_offset().x;
- int svofs = (size.height + vofs) / 2 - 1;
- Color c = fc;
- c.a = 0.2;
-
- draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
- int gofs = get_as_ratio() * width;
- c.a = 0.9;
- Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE);
- draw_rect(grabber_rect, c);
-
- grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.position + grabber_rect.size * 0.5;
-
- bool display_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();
- } else {
- grabber->hide();
- }
- }
-
- if (display_grabber) {
- Ref<Texture2D> grabber_tex;
- if (mouse_over_grabber) {
- grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
- } else {
- grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
- }
+ }
- if (grabber->get_texture() != grabber_tex) {
- grabber->set_texture(grabber_tex);
- }
+ if (display_grabber) {
+ Ref<Texture2D> grabber_tex;
+ if (mouse_over_grabber) {
+ grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
+ } else {
+ grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
+ }
- Vector2 scale = get_global_transform_with_canvas().get_scale();
- grabber->set_scale(scale);
- grabber->set_size(Size2(0, 0));
- grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale);
+ if (grabber->get_texture() != grabber_tex) {
+ grabber->set_texture(grabber_tex);
+ }
- if (mousewheel_over_grabber) {
- Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size);
- }
+ Vector2 scale = get_global_transform_with_canvas().get_scale();
+ grabber->set_scale(scale);
+ grabber->set_size(Size2(0, 0));
+ grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale);
- grabber_range = width;
+ if (mousewheel_over_grabber) {
+ Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size);
}
+
+ grabber_range = width;
}
}
+}
- if (p_what == NOTIFICATION_MOUSE_ENTER) {
- mouse_over_spin = true;
- update();
- }
- if (p_what == NOTIFICATION_MOUSE_EXIT) {
- mouse_over_spin = false;
- update();
- }
- if (p_what == NOTIFICATION_FOCUS_ENTER) {
- if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) {
- _focus_entered();
- }
- value_input_just_closed = false;
+void EditorSpinSlider::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ _update_value_input_stylebox();
+ break;
+
+ case NOTIFICATION_DRAW:
+ _draw_spin_slider();
+ break;
+
+ case NOTIFICATION_WM_WINDOW_FOCUS_IN:
+ case NOTIFICATION_WM_WINDOW_FOCUS_OUT:
+ case NOTIFICATION_EXIT_TREE:
+ if (grabbing_spinner) {
+ grabber->hide();
+ Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
+ grabbing_spinner = false;
+ grabbing_spinner_attempt = false;
+ }
+ break;
+
+ case NOTIFICATION_MOUSE_ENTER:
+ mouse_over_spin = true;
+ update();
+ break;
+ case NOTIFICATION_MOUSE_EXIT:
+ mouse_over_spin = false;
+ update();
+ break;
+ case NOTIFICATION_FOCUS_ENTER:
+ if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) {
+ _focus_entered();
+ }
+ value_input_just_closed = false;
+ break;
}
}
@@ -567,8 +576,10 @@ void EditorSpinSlider::_ensure_input_popup() {
if (value_input_popup) {
return;
}
+
value_input_popup = memnew(Popup);
add_child(value_input_popup);
+
value_input = memnew(LineEdit);
value_input_popup->add_child(value_input);
value_input_popup->set_wrap_controls(true);
@@ -576,6 +587,7 @@ void EditorSpinSlider::_ensure_input_popup() {
value_input_popup->connect("popup_hide", callable_mp(this, &EditorSpinSlider::_value_input_closed));
value_input->connect("text_submitted", callable_mp(this, &EditorSpinSlider::_value_input_submitted));
value_input->connect("focus_exited", callable_mp(this, &EditorSpinSlider::_value_focus_exited));
+
if (is_inside_tree()) {
_update_value_input_stylebox();
}
diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h
index 5b99f88505..c09d084e88 100644
--- a/editor/editor_spin_slider.h
+++ b/editor/editor_spin_slider.h
@@ -81,6 +81,7 @@ class EditorSpinSlider : public Range {
void _update_value_input_stylebox();
void _ensure_input_popup();
+ void _draw_spin_slider();
protected:
void _notification(int p_what);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 2c83fb2df6..4496180ddd 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -219,6 +219,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme =
exceptions.insert("DefaultProjectIcon");
exceptions.insert("GuiChecked");
exceptions.insert("GuiRadioChecked");
+ exceptions.insert("GuiIndeterminate");
exceptions.insert("GuiCloseCustomizable");
exceptions.insert("GuiGraphNodePort");
exceptions.insert("GuiResizer");
@@ -595,6 +596,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("panel", "PanelContainer", style_menu);
theme->set_stylebox("MenuPanel", "EditorStyles", style_menu);
+ // CanvasItem Editor
+ Ref<StyleBoxFlat> style_canvas_editor_info = make_flat_stylebox(Color(0.0, 0.0, 0.0, 0.2));
+ style_canvas_editor_info->set_expand_margin_size_all(4 * EDSCALE);
+ theme->set_stylebox("CanvasItemInfoOverlay", "EditorStyles", style_canvas_editor_info);
+
// Script Editor
theme->set_stylebox("ScriptEditorPanel", "EditorStyles", make_empty_stylebox(default_margin_size, 0, default_margin_size, default_margin_size));
theme->set_stylebox("ScriptEditor", "EditorStyles", make_empty_stylebox(0, 0, 0, 0));
@@ -812,6 +818,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// Tree
theme->set_icon("checked", "Tree", theme->get_icon("GuiChecked", "EditorIcons"));
+ theme->set_icon("indeterminate", "Tree", theme->get_icon("GuiIndeterminate", "EditorIcons"));
theme->set_icon("unchecked", "Tree", theme->get_icon("GuiUnchecked", "EditorIcons"));
theme->set_icon("arrow", "Tree", theme->get_icon("GuiTreeArrowDown", "EditorIcons"));
theme->set_icon("arrow_collapsed", "Tree", theme->get_icon("GuiTreeArrowRight", "EditorIcons"));
@@ -1499,15 +1506,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
}
Ref<Theme> create_custom_theme(const Ref<Theme> p_theme) {
- Ref<Theme> theme;
-
- const String custom_theme = EditorSettings::get_singleton()->get("interface/theme/custom_theme");
- if (custom_theme != "") {
- theme = ResourceLoader::load(custom_theme);
- }
+ Ref<Theme> theme = create_editor_theme(p_theme);
- if (!theme.is_valid()) {
- theme = create_editor_theme(p_theme);
+ const String custom_theme_path = EditorSettings::get_singleton()->get("interface/theme/custom_theme");
+ if (custom_theme_path != "") {
+ Ref<Theme> custom_theme = ResourceLoader::load(custom_theme_path);
+ if (custom_theme.is_valid()) {
+ theme->merge_with(custom_theme);
+ }
}
return theme;
diff --git a/editor/icons/GuiIndeterminate.svg b/editor/icons/GuiIndeterminate.svg
new file mode 100644
index 0000000000..79e5d95c9a
--- /dev/null
+++ b/editor/icons/GuiIndeterminate.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.288 0-2.333 1.045-2.333 2.333v9.334c0 1.288 1.045 2.333 2.333 2.333h9.334c1.288 0 2.333-1.045 2.333-2.333v-9.334c0-1.288-1.045-2.333-2.333-2.333z" fill="#699ce8" fill-rule="nonzero"/><path d="m3 7h10.058v2.12h-10.058z" fill="#fff" transform="matrix(.99428 0 0 .943295 .017161 .396936)"/></svg>
diff --git a/editor/icons/VisualShaderNodeTexture2DUniformTriplanar.svg b/editor/icons/VisualShaderNodeTexture2DArrayUniform.svg
index ed9e084fd3..ed9e084fd3 100644
--- a/editor/icons/VisualShaderNodeTexture2DUniformTriplanar.svg
+++ b/editor/icons/VisualShaderNodeTexture2DArrayUniform.svg
diff --git a/editor/icons/VisualShaderNodeTexture3DUniform.svg b/editor/icons/VisualShaderNodeTexture3DUniform.svg
new file mode 100644
index 0000000000..ed9e084fd3
--- /dev/null
+++ b/editor/icons/VisualShaderNodeTexture3DUniform.svg
@@ -0,0 +1 @@
+<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg>
diff --git a/editor/icons/VisualShaderNodeTextureUniform.svg b/editor/icons/VisualShaderNodeTextureUniform.svg
new file mode 100644
index 0000000000..ed9e084fd3
--- /dev/null
+++ b/editor/icons/VisualShaderNodeTextureUniform.svg
@@ -0,0 +1 @@
+<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg>
diff --git a/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg b/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg
new file mode 100644
index 0000000000..ed9e084fd3
--- /dev/null
+++ b/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg
@@ -0,0 +1 @@
+<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg>
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 76c056ed33..477e066e87 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3909,6 +3909,11 @@ void CanvasItemEditor::_notification(int p_what) {
anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE);
anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")));
+
+ info_overlay->get_theme()->set_stylebox("normal", "Label", get_theme_stylebox(SNAME("CanvasItemInfoOverlay"), SNAME("EditorStyles")));
+ warning_child_of_container->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor")));
+ warning_child_of_container->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts")));
+ warning_child_of_container->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));
}
if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
@@ -5280,21 +5285,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
info_overlay->add_theme_constant_override("separation", 10);
viewport_scrollable->add_child(info_overlay);
+ // Make sure all labels inside of the container are styled the same.
Theme *info_overlay_theme = memnew(Theme);
- info_overlay_theme->copy_default_theme();
info_overlay->set_theme(info_overlay_theme);
- StyleBoxFlat *info_overlay_label_stylebox = memnew(StyleBoxFlat);
- info_overlay_label_stylebox->set_bg_color(Color(0.0, 0.0, 0.0, 0.2));
- info_overlay_label_stylebox->set_expand_margin_size_all(4);
- info_overlay_theme->set_stylebox("normal", "Label", info_overlay_label_stylebox);
-
warning_child_of_container = memnew(Label);
warning_child_of_container->hide();
warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent."));
- warning_child_of_container->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")));
- warning_child_of_container->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts")));
- warning_child_of_container->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));
add_control_to_info_overlay(warning_child_of_container);
h_scroll = memnew(HScrollBar);
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index b93e12d7fa..38510ff81e 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -2104,7 +2104,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
grests.resize(skel->get_bone_count());
LocalVector<int> bones;
- LocalVector<real_t> weights;
+ LocalVector<float> weights;
bones.resize(4);
weights.resize(4);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index d29c1efabb..868b834993 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -532,7 +532,7 @@ private:
bool grid_enable[3]; //should be always visible if true
bool grid_enabled;
bool grid_init_draw = false;
- Camera3D::Projection grid_camera_last_update_perspective;
+ Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE;
Vector3 grid_camera_last_update_position = Vector3();
Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3];
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 226a54b966..bd90fa82f2 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -942,7 +942,10 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) {
}
_update_script_names();
+ _trigger_live_script_reload();
+}
+void ScriptEditor::_trigger_live_script_reload() {
if (!pending_auto_reload && auto_reload_running_scripts) {
call_deferred(SNAME("_live_auto_reload_running_scripts"));
pending_auto_reload = true;
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 72a649ffbf..e322828b6c 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -344,6 +344,7 @@ class ScriptEditor : public PanelContainer {
bool pending_auto_reload;
bool auto_reload_running_scripts;
+ void _trigger_live_script_reload();
void _live_auto_reload_running_scripts();
void _update_selected_editor_menu();
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 4491c13b4c..7f2908eada 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -666,6 +666,8 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo
script->set_source_code(rel_script->get_source_code());
script->set_last_modified_time(rel_script->get_last_modified_time());
script->update_exports();
+
+ _trigger_live_script_reload();
}
}
}
@@ -1628,6 +1630,13 @@ void ScriptTextEditor::_color_changed(const Color &p_color) {
code_editor->get_text_editor()->update();
}
+void ScriptTextEditor::_prepare_edit_menu() {
+ const CodeEdit *tx = code_editor->get_text_editor();
+ PopupMenu *popup = edit_menu->get_popup();
+ popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo());
+ popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo());
+}
+
void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) {
context_menu->clear();
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
@@ -1667,6 +1676,10 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p
}
}
+ const CodeEdit *tx = code_editor->get_text_editor();
+ context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo());
+ context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo());
+
context_menu->set_position(get_global_transform().xform(p_pos));
context_menu->set_size(Vector2(1, 1));
context_menu->popup();
@@ -1752,6 +1765,7 @@ void ScriptTextEditor::_enable_code_editor() {
search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
edit_hb->add_child(edit_menu);
+ edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu));
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();
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index e4a13951e4..1ca6f56ea1 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -178,6 +178,7 @@ protected:
void _make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos);
void _text_edit_gui_input(const Ref<InputEvent> &ev);
void _color_changed(const Color &p_color);
+ void _prepare_edit_menu();
void _goto_line(int p_line) { goto_line(p_line); }
void _lookup_symbol(const String &p_symbol, int p_row, int p_column);
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index 481eb84081..32bcc1a4e6 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -471,6 +471,13 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
}
}
+void TextEditor::_prepare_edit_menu() {
+ const CodeEdit *tx = code_editor->get_text_editor();
+ PopupMenu *popup = edit_menu->get_popup();
+ popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo());
+ popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo());
+}
+
void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) {
context_menu->clear();
if (p_selection) {
@@ -497,6 +504,10 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);
}
+ const CodeEdit *tx = code_editor->get_text_editor();
+ context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo());
+ context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo());
+
context_menu->set_position(get_global_transform().xform(p_position));
context_menu->set_size(Vector2(1, 1));
context_menu->popup();
@@ -542,6 +553,7 @@ TextEditor::TextEditor() {
edit_hb->add_child(edit_menu);
edit_menu->set_text(TTR("Edit"));
edit_menu->set_switch_on_hover(true);
+ edit_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_prepare_edit_menu));
edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option));
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 86a4910ac0..839e1c5f7a 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -92,6 +92,7 @@ protected:
void _edit_option(int p_op);
void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position);
void _text_edit_gui_input(const Ref<InputEvent> &ev);
+ void _prepare_edit_menu();
Map<String, Ref<EditorSyntaxHighlighter>> highlighters;
void _change_syntax_highlighter(int p_idx);
diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index 83b0203f32..e7ba80677d 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -1206,11 +1206,10 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope
}
tree->connect("cell_selected", callable_mp(this, &SceneTreeEditor::_selected_changed));
- tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed), varray(), CONNECT_DEFERRED);
+ tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed));
tree->connect("multi_selected", callable_mp(this, &SceneTreeEditor::_cell_multi_selected));
tree->connect("button_pressed", callable_mp(this, &SceneTreeEditor::_cell_button_pressed));
tree->connect("nothing_selected", callable_mp(this, &SceneTreeEditor::_deselect_items));
- //tree->connect("item_edited", this,"_renamed",Vector<Variant>(),true);
error = memnew(AcceptDialog);
add_child(error);
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 1a844bf241..07f50d14dc 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -200,7 +200,9 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
if (singleton->full_gdscript_cache.has(p_path)) {
return singleton->full_gdscript_cache[p_path];
}
+
Ref<GDScript> script = get_shallow_script(p_path);
+ ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>());
r_error = script->load_source_code(p_path);
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index b9004c4989..e03375e8d9 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -377,7 +377,7 @@ MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int a
if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) {
godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1;
}
- if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) {
+ if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) {
godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2;
}
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 17ee173855..8ed2d4e60a 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -717,6 +717,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared
return OK;
}
+bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) {
+ return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1;
+}
+
void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
const char **aperms = android_perms;
while (*aperms) {
@@ -763,12 +767,17 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
Vector<String> perms;
_get_permissions(p_preset, p_give_internet, perms);
for (int i = 0; i < perms.size(); i++) {
- manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ String permission = perms.get(i);
+ if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission);
+ } else {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission);
+ }
}
manifest_text += _get_xr_features_tag(p_preset);
manifest_text += _get_instrumentation_tag(p_preset);
- manifest_text += _get_application_tag(p_preset);
+ manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));
manifest_text += "</manifest>\n";
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
@@ -824,6 +833,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<String> perms;
// Write permissions into the perms variable.
_get_permissions(p_preset, p_give_internet, perms);
+ bool has_storage_permission = _has_storage_permission(perms);
while (ofs < (uint32_t)p_manifest.size()) {
uint32_t chunk = decode_uint32(&p_manifest[ofs]);
@@ -913,6 +923,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
}
+ if (tname == "application" && attrname == "requestLegacyExternalStorage") {
+ encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+ }
+
if (tname == "application" && attrname == "allowBackup") {
encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]);
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 909428c2fe..b061ee4e04 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -134,6 +134,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so);
+ bool _has_storage_permission(const Vector<String> &p_permissions);
+
void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions);
void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index 76512226bf..b9e28a7937 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -235,18 +235,20 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
return manifest_activity_text;
}
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset) {
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) {
String manifest_application_text = vformat(
" <application android:label=\"@string/godot_project_name_string\"\n"
" android:allowBackup=\"%s\"\n"
" android:icon=\"@mipmap/icon\"\n"
" android:isGame=\"%s\"\n"
" android:hasFragileUserData=\"%s\"\n"
- " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData\"\n"
+ " android:requestLegacyExternalStorage=\"%s\"\n"
+ " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
" tools:ignore=\"GoogleAppIndexingWarning\">\n\n",
bool_to_string(p_preset->get("user_data_backup/allow")),
bool_to_string(p_preset->get("package/classify_as_game")),
- bool_to_string(p_preset->get("package/retain_data_on_uninstall")));
+ bool_to_string(p_preset->get("package/retain_data_on_uninstall")),
+ bool_to_string(p_has_storage_permission));
manifest_application_text += _get_activity_tag(p_preset);
manifest_application_text += " </application>\n";
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 44e9a1727d..8a93c25d79 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -81,6 +81,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset);
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset);
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 467a0dc3c0..00e01884cf 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -22,6 +22,7 @@
android:icon="@mipmap/icon"
android:isGame="true"
android:hasFragileUserData="false"
+ android:requestLegacyExternalStorage="false"
tools:ignore="GoogleAppIndexingWarning" >
<!-- Records the version of the Godot editor used for building -->
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 81fc87b7ef..fad64c675f 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,8 +1,8 @@
ext.versions = [
- androidGradlePlugin: '4.2.1',
- compileSdk : 29,
- minSdk : 18,
- targetSdk : 29,
+ androidGradlePlugin: '4.2.2',
+ compileSdk : 30,
+ minSdk : 19,
+ targetSdk : 30,
buildTools : '30.0.3',
supportCoreUtils : '1.0.0',
kotlinVersion : '1.5.10',
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 76751a886c..317175858b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -49,7 +49,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -68,15 +67,12 @@ import android.os.Environment;
import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
-import android.provider.Settings.Secure;
import android.view.Display;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
@@ -471,7 +467,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
io = new GodotIO(activity);
- io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 66882e8e72..d85d88ec6c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -30,15 +30,19 @@
package org.godotengine.godot;
-import org.godotengine.godot.input.*;
+import org.godotengine.godot.input.GodotEditText;
import android.app.Activity;
-import android.content.*;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Point;
import android.net.Uri;
-import android.os.*;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
@@ -47,14 +51,16 @@ import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Locale;
// Wrapper for native library
public class GodotIO {
- AssetManager am;
- final Activity activity;
+ private static final String TAG = GodotIO.class.getSimpleName();
+
+ private final AssetManager am;
+ private final Activity activity;
+ private final String uniqueId;
GodotEditText edit;
final int SCREEN_LANDSCAPE = 0;
@@ -66,167 +72,6 @@ public class GodotIO {
final int SCREEN_SENSOR = 6;
/////////////////////////
- /// FILES
- /////////////////////////
-
- public int last_file_id = 1;
-
- static class AssetData {
- public boolean eof = false;
- public String path;
- public InputStream is;
- public int len;
- public int pos;
- }
-
- SparseArray<AssetData> streams;
-
- public int file_open(String path, boolean write) {
- //System.out.printf("file_open: Attempt to Open %s\n",path);
-
- //Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
- if (write)
- return -1;
-
- AssetData ad = new AssetData();
-
- try {
- ad.is = am.open(path);
-
- } catch (Exception e) {
- //System.out.printf("Exception on file_open: %s\n",path);
- return -1;
- }
-
- try {
- ad.len = ad.is.available();
- } catch (Exception e) {
- System.out.printf("Exception availabling on file_open: %s\n", path);
- return -1;
- }
-
- ad.path = path;
- ad.pos = 0;
- ++last_file_id;
- streams.put(last_file_id, ad);
-
- return last_file_id;
- }
- public int file_get_size(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return -1;
- }
-
- return streams.get(id).len;
- }
- public void file_seek(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return;
- }
- //seek sucks
- AssetData ad = streams.get(id);
- if (bytes > ad.len)
- bytes = ad.len;
- if (bytes < 0)
- bytes = 0;
-
- try {
- if (bytes > (int)ad.pos) {
- int todo = bytes - (int)ad.pos;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- ad.pos = bytes;
- } else if (bytes < (int)ad.pos) {
- ad.is = am.open(ad.path);
-
- ad.pos = bytes;
- int todo = bytes;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- }
-
- ad.eof = false;
- } catch (IOException e) {
- System.out.printf("Exception on file_seek: %s\n", e);
- return;
- }
- }
-
- public int file_tell(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id);
- return 0;
- }
-
- AssetData ad = streams.get(id);
- return ad.pos;
- }
- public boolean file_eof(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id);
- return false;
- }
-
- AssetData ad = streams.get(id);
- return ad.eof;
- }
-
- public byte[] file_read(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't read invalid file id: %d\n", id);
- return new byte[0];
- }
-
- AssetData ad = streams.get(id);
-
- if (ad.pos + bytes > ad.len) {
- bytes = ad.len - ad.pos;
- ad.eof = true;
- }
-
- if (bytes == 0) {
- return new byte[0];
- }
-
- byte[] buf1 = new byte[bytes];
- int r = 0;
- try {
- r = ad.is.read(buf1);
- } catch (IOException e) {
- System.out.printf("Exception on file_read: %s\n", e);
- return new byte[bytes];
- }
-
- if (r == 0) {
- return new byte[0];
- }
-
- ad.pos += r;
-
- if (r < bytes) {
- byte[] buf2 = new byte[r];
- for (int i = 0; i < r; i++)
- buf2[i] = buf1[i];
- return buf2;
- } else {
- return buf1;
- }
- }
-
- public void file_close(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_close: Can't close invalid file id: %d\n", id);
- return;
- }
-
- streams.remove(id);
- }
-
- /////////////////////////
/// DIRECTORIES
/////////////////////////
@@ -236,9 +81,9 @@ public class GodotIO {
public String path;
}
- public int last_dir_id = 1;
+ private int last_dir_id = 1;
- SparseArray<AssetDir> dirs;
+ private final SparseArray<AssetDir> dirs;
public int dir_open(String path) {
AssetDir ad = new AssetDir();
@@ -257,7 +102,6 @@ public class GodotIO {
return -1;
}
- //System.out.printf("Opened dir: %s\n",path);
++last_dir_id;
dirs.put(last_dir_id, ad);
@@ -320,9 +164,14 @@ public class GodotIO {
GodotIO(Activity p_activity) {
am = p_activity.getAssets();
activity = p_activity;
- //streams = new HashMap<Integer, AssetData>();
- streams = new SparseArray<>();
dirs = new SparseArray<>();
+ String androidId = Settings.Secure.getString(activity.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ if (androidId == null) {
+ androidId = "";
+ }
+
+ uniqueId = androidId;
}
/////////////////////////
@@ -331,7 +180,6 @@ public class GodotIO {
public int openURI(String p_uri) {
try {
- Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
String path = p_uri;
String type = "";
if (path.startsWith("/")) {
@@ -357,12 +205,12 @@ public class GodotIO {
}
}
- public String getDataDir() {
- return activity.getFilesDir().getAbsolutePath();
+ public String getCacheDir() {
+ return activity.getCacheDir().getAbsolutePath();
}
- public String getExternalDataDir() {
- return activity.getExternalFilesDir(null).getAbsolutePath();
+ public String getDataDir() {
+ return activity.getFilesDir().getAbsolutePath();
}
public String getLocale() {
@@ -456,51 +304,58 @@ public class GodotIO {
public static final int SYSTEM_DIR_PICTURES = 6;
public static final int SYSTEM_DIR_RINGTONES = 7;
- public String getSystemDir(int idx) {
- String what = "";
+ public String getSystemDir(int idx, boolean shared_storage) {
+ String what;
switch (idx) {
- case SYSTEM_DIR_DESKTOP: {
- //what=Environment.DIRECTORY_DOCUMENTS;
- what = Environment.DIRECTORY_DOWNLOADS;
+ case SYSTEM_DIR_DESKTOP:
+ default: {
+ what = null; // This leads to the app specific external root directory.
} break;
+
case SYSTEM_DIR_DCIM: {
what = Environment.DIRECTORY_DCIM;
-
} break;
+
case SYSTEM_DIR_DOCUMENTS: {
- what = Environment.DIRECTORY_DOWNLOADS;
- //what=Environment.DIRECTORY_DOCUMENTS;
+ what = Environment.DIRECTORY_DOCUMENTS;
} break;
+
case SYSTEM_DIR_DOWNLOADS: {
what = Environment.DIRECTORY_DOWNLOADS;
-
} break;
+
case SYSTEM_DIR_MOVIES: {
what = Environment.DIRECTORY_MOVIES;
-
} break;
+
case SYSTEM_DIR_MUSIC: {
what = Environment.DIRECTORY_MUSIC;
} break;
+
case SYSTEM_DIR_PICTURES: {
what = Environment.DIRECTORY_PICTURES;
} break;
+
case SYSTEM_DIR_RINGTONES: {
what = Environment.DIRECTORY_RINGTONES;
-
} break;
}
- if (what.equals(""))
- return "";
- return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ if (shared_storage) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Log.w(TAG, "Shared storage access is limited on Android 10 and higher.");
+ }
+ if (TextUtils.isEmpty(what)) {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ } else {
+ return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ }
+ } else {
+ return activity.getExternalFilesDir(what).getAbsolutePath();
+ }
}
- protected static final String PREFS_FILE = "device_id.xml";
- protected static final String PREFS_DEVICE_ID = "device_id";
-
- public static String unique_id = "";
public String getUniqueID() {
- return unique_id;
+ return uniqueId;
}
}
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 5e99135498..5cd2c382d2 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -48,8 +48,8 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I");
+ _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");
_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;");
- _get_external_data_dir = p_env->GetMethodID(cls, "getExternalDataDir", "()Ljava/lang/String;");
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
@@ -59,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
- _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
+ _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
}
}
@@ -82,22 +82,22 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
}
}
-String GodotIOJavaWrapper::get_user_data_dir() {
- if (_get_data_dir) {
+String GodotIOJavaWrapper::get_cache_dir() {
+ if (_get_cache_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String());
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir);
return jstring_to_string(s, env);
} else {
return String();
}
}
-String GodotIOJavaWrapper::get_external_data_dir() {
- if (_get_external_data_dir) {
+String GodotIOJavaWrapper::get_user_data_dir() {
+ if (_get_data_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String());
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_external_data_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
return jstring_to_string(s, env);
} else {
return String();
@@ -200,11 +200,11 @@ int GodotIOJavaWrapper::get_screen_orientation() {
}
}
-String GodotIOJavaWrapper::get_system_dir(int p_dir) {
+String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String("."));
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage);
return jstring_to_string(s, env);
} else {
return String(".");
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index e4c0a4b2c7..8f6d7f813f 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -46,8 +46,8 @@ private:
jclass cls;
jmethodID _open_URI = 0;
+ jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
- jmethodID _get_external_data_dir = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
@@ -66,8 +66,8 @@ public:
jobject get_instance();
Error open_uri(const String &p_uri);
+ String get_cache_dir();
String get_user_data_dir();
- String get_external_data_dir();
String get_locale();
String get_model();
int get_screen_dpi();
@@ -80,7 +80,7 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation();
- String get_system_dir(int p_dir);
+ String get_system_dir(int p_dir, bool p_shared_storage);
};
#endif /* !JAVA_GODOT_IO_WRAPPER_H */
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index c2e12442b3..21fb31d991 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -213,6 +213,10 @@ String OS_Android::get_model_name() const {
return OS_Unix::get_model_name();
}
+String OS_Android::get_data_path() const {
+ return get_user_data_dir();
+}
+
String OS_Android::get_user_data_dir() const {
if (data_dir_cache != String())
return data_dir_cache;
@@ -225,11 +229,11 @@ String OS_Android::get_user_data_dir() const {
return ".";
}
-String OS_Android::get_external_data_dir() const {
- String data_dir = godot_io_java->get_external_data_dir();
- if (data_dir != "") {
- data_dir = _remove_symlink(data_dir);
- return data_dir;
+String OS_Android::get_cache_path() const {
+ String cache_dir = godot_io_java->get_cache_dir();
+ if (cache_dir != "") {
+ cache_dir = _remove_symlink(cache_dir);
+ return cache_dir;
}
return ".";
}
@@ -242,8 +246,8 @@ String OS_Android::get_unique_id() const {
return OS::get_unique_id();
}
-String OS_Android::get_system_dir(SystemDir p_dir) const {
- return godot_io_java->get_system_dir(p_dir);
+String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
+ return godot_io_java->get_system_dir(p_dir, p_shared_storage);
}
void OS_Android::set_display_size(const Size2i &p_size) {
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index a5b995a775..c938297821 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -110,14 +110,15 @@ public:
virtual Error shell_open(String p_uri) override;
virtual String get_user_data_dir() const override;
- virtual String get_external_data_dir() const override;
+ virtual String get_data_path() const override;
+ virtual String get_cache_path() const override;
virtual String get_resource_dir() const override;
virtual String get_locale() const override;
virtual String get_model_name() const override;
virtual String get_unique_id() const override;
- virtual String get_system_dir(SystemDir p_dir) const override;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
void vibrate_handheld(int p_duration_ms) override;
diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js
index 5aa750757c..99e7ee8b5f 100644
--- a/platform/javascript/js/libs/library_godot_os.js
+++ b/platform/javascript/js/libs/library_godot_os.js
@@ -106,7 +106,7 @@ autoAddDeps(GodotConfig, '$GodotConfig');
mergeInto(LibraryManager.library, GodotConfig);
const GodotFS = {
- $GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'],
+ $GodotFS__deps: ['$ERRNO_CODES', '$FS', '$IDBFS', '$GodotRuntime'],
$GodotFS__postset: [
'Module["initFS"] = GodotFS.init;',
'Module["copyToFS"] = GodotFS.copy_to_fs;',
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index 08630be8b0..2c9801f512 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -273,7 +273,7 @@ String OS_LinuxBSD::get_cache_path() const {
}
}
-String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const {
+String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
String xdgparam;
switch (p_dir) {
diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h
index 1e06587322..35c80e3f9b 100644
--- a/platform/linuxbsd/os_linuxbsd.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -84,7 +84,7 @@ public:
virtual String get_data_path() const override;
virtual String get_cache_path() const override;
- virtual String get_system_dir(SystemDir p_dir) const override;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
virtual Error shell_open(String p_uri) override;
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 37d30add78..df41ccd892 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -84,7 +84,7 @@ public:
virtual String get_bundle_resource_dir() const override;
virtual String get_godot_dir_name() const override;
- virtual String get_system_dir(SystemDir p_dir) const override;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
Error shell_open(String p_uri) override;
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index c458a0264a..c6e35fee83 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -395,7 +395,7 @@ String OS_OSX::get_godot_dir_name() const {
return String(VERSION_SHORT_NAME).capitalize();
}
-String OS_OSX::get_system_dir(SystemDir p_dir) const {
+String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
NSSearchPathDirectory id;
bool found = true;
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 2c8afaf7de..2a0a509d43 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -681,7 +681,7 @@ String OS_Windows::get_godot_dir_name() const {
return String(VERSION_SHORT_NAME).capitalize();
}
-String OS_Windows::get_system_dir(SystemDir p_dir) const {
+String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
KNOWNFOLDERID id;
switch (p_dir) {
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index ea0c263b78..c4a2eda8f4 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -150,7 +150,7 @@ public:
virtual String get_cache_path() const override;
virtual String get_godot_dir_name() const override;
- virtual String get_system_dir(SystemDir p_dir) const override;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
virtual String get_user_data_dir() const override;
virtual String get_unique_id() const override;
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 00da94c3b0..dd1a4671d9 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -1104,14 +1104,12 @@ bool CharacterBody2D::move_and_slide() {
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer2D::MotionResult result;
- bool found_collision = false;
Vector2 prev_position = get_global_transform().elements[2];
bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled);
if (collided) {
- found_collision = true;
motion_results.push_back(result);
_set_collision_direction(result);
@@ -1136,7 +1134,7 @@ bool CharacterBody2D::move_and_slide() {
// Move on floor only checks.
if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) {
// Avoid to move forward on a wall if floor_block_on_wall is true.
- if (was_on_floor && !is_on_floor_only() && !vel_dir_facing_up) {
+ if (was_on_floor && !on_floor && !vel_dir_facing_up) {
// If the movement is large the body can be prevented from reaching the walls.
if (result.travel.length() <= margin) {
// Cancels the motion.
@@ -1155,7 +1153,7 @@ bool CharacterBody2D::move_and_slide() {
break;
}
// Prevents the body from being able to climb a slope when it moves forward against the wall.
- else if (!is_on_floor_only()) {
+ else if (!on_floor) {
motion = up_direction * up_direction.dot(result.remainder);
motion = motion.slide(result.collision_normal);
} else {
@@ -1166,9 +1164,7 @@ bool CharacterBody2D::move_and_slide() {
else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && was_on_floor && motion.dot(result.collision_normal) < 0) {
can_apply_constant_speed = false;
Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized();
- if (!motion_slide_norm.is_equal_approx(Vector2())) {
- motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length());
- }
+ motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length());
}
// Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling.
else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up)) {
@@ -1209,17 +1205,15 @@ bool CharacterBody2D::move_and_slide() {
set_global_transform(gt);
Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized();
- if (!motion_slide_norm.is_equal_approx(Vector2())) {
- motion = motion_slide_norm * (motion_slide_up.length());
- found_collision = true;
- }
+ motion = motion_slide_norm * (motion_slide_up.length());
+ collided = true;
}
can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;
sliding_enabled = true;
first_slide = false;
- if (!found_collision || motion.is_equal_approx(Vector2())) {
+ if (!collided || motion.is_equal_approx(Vector2())) {
break;
}
}
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 610974ff90..092efc55d7 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -1176,11 +1176,6 @@ bool CharacterBody3D::move_and_slide() {
}
}
- if (!on_floor && !on_wall) {
- // Add last platform velocity when just left a moving platform.
- linear_velocity += current_floor_velocity;
- }
-
if (was_on_floor && snap != Vector3()) {
// Apply snap.
Transform3D gt = get_global_transform();
@@ -1213,6 +1208,11 @@ bool CharacterBody3D::move_and_slide() {
}
}
+ if (!on_floor && !on_wall) {
+ // Add last platform velocity when just left a moving platform.
+ linear_velocity += current_floor_velocity;
+ }
+
return motion_results.size() > 0;
}
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 857916e23d..94fb49ae81 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -173,12 +173,15 @@ void Skeleton3D::_update_process_order() {
parentless_bones.clear();
for (int i = 0; i < len; i++) {
+ bonesptr[i].child_bones.clear();
+ }
+
+ for (int i = 0; i < len; i++) {
if (bonesptr[i].parent >= len) {
//validate this just in case
ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent));
bonesptr[i].parent = -1;
}
- bonesptr[i].child_bones.clear();
if (bonesptr[i].parent != -1) {
int parent_bone_idx = bonesptr[i].parent;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 2b3be1d5c2..11e08b231e 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -946,6 +946,17 @@ void LineEdit::paste_text() {
}
}
+bool LineEdit::has_undo() const {
+ if (undo_stack_pos == nullptr) {
+ return undo_stack.size() > 1;
+ }
+ return undo_stack_pos != undo_stack.front();
+}
+
+bool LineEdit::has_redo() const {
+ return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back();
+}
+
void LineEdit::undo() {
if (!editable) {
return;
@@ -2277,6 +2288,11 @@ void LineEdit::_ensure_menu() {
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
+
+ if (editable) {
+ menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo());
+ menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo());
+ }
}
LineEdit::LineEdit() {
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index c5c92d60aa..0e9c032e88 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -285,6 +285,8 @@ public:
void copy_text();
void cut_text();
void paste_text();
+ bool has_undo() const;
+ bool has_redo() const;
void undo();
void redo();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index a2c5769947..87f06445ac 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -2963,6 +2963,18 @@ void TextEdit::end_complex_operation() {
undo_stack.back()->get().chain_backward = true;
}
+bool TextEdit::has_undo() const {
+ if (undo_stack_pos == nullptr) {
+ int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1;
+ return undo_stack.size() + pending > 0;
+ }
+ return undo_stack_pos != undo_stack.front();
+}
+
+bool TextEdit::has_redo() const {
+ return undo_stack_pos != nullptr;
+}
+
void TextEdit::undo() {
if (!editable) {
return;
@@ -4482,6 +4494,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation);
ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation);
+ ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo);
+ ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo);
ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo);
ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo);
ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history);
@@ -4906,6 +4920,11 @@ void TextEdit::_backspace() {
return;
}
+ if (has_selection()) {
+ delete_selection();
+ return;
+ }
+
int cc = get_caret_column();
int cl = get_caret_line();
@@ -4913,11 +4932,6 @@ void TextEdit::_backspace() {
return;
}
- if (has_selection()) {
- delete_selection();
- return;
- }
-
int prev_line = cc ? cl : cl - 1;
int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
@@ -5070,6 +5084,11 @@ void TextEdit::_generate_context_menu() {
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
+
+ if (editable) {
+ menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo());
+ menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo());
+ }
}
int TextEdit::_get_menu_action_accelerator(const String &p_action) {
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index da322a7bcd..69468978ab 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -659,6 +659,8 @@ public:
void begin_complex_operation();
void end_complex_operation();
+ bool has_undo() const;
+ bool has_redo() const;
void undo();
void redo();
void clear_undo_history();
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 09db3205d9..d9892b53fc 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -167,6 +167,18 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const {
void TreeItem::set_checked(int p_column, bool p_checked) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].checked = p_checked;
+ cells.write[p_column].indeterminate = false;
+ _changed_notify(p_column);
+}
+
+void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ // Prevent uncheck if indeterminate set to false twice
+ if (p_indeterminate == cells[p_column].indeterminate) {
+ return;
+ }
+ cells.write[p_column].indeterminate = p_indeterminate;
+ cells.write[p_column].checked = false;
_changed_notify(p_column);
}
@@ -175,6 +187,11 @@ bool TreeItem::is_checked(int p_column) const {
return cells[p_column].checked;
}
+bool TreeItem::is_indeterminate(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), false);
+ return cells[p_column].indeterminate;
+}
+
void TreeItem::set_text(int p_column, String p_text) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].text = p_text;
@@ -1042,7 +1059,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode);
ClassDB::bind_method(D_METHOD("set_checked", "column", "checked"), &TreeItem::set_checked);
+ ClassDB::bind_method(D_METHOD("set_indeterminate", "column", "indeterminate"), &TreeItem::set_indeterminate);
ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked);
+ ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate);
ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text);
ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text);
@@ -1228,6 +1247,7 @@ void Tree::update_cache() {
cache.checked = get_theme_icon(SNAME("checked"));
cache.unchecked = get_theme_icon(SNAME("unchecked"));
+ cache.indeterminate = get_theme_icon(SNAME("indeterminate"));
if (is_layout_rtl()) {
cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored"));
} else {
@@ -1721,10 +1741,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
case TreeItem::CELL_MODE_CHECK: {
Ref<Texture2D> checked = cache.checked;
Ref<Texture2D> unchecked = cache.unchecked;
+ Ref<Texture2D> indeterminate = cache.indeterminate;
Point2i check_ofs = item_rect.position;
check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2);
- if (p_item->cells[i].checked) {
+ if (p_item->cells[i].indeterminate) {
+ indeterminate->draw(ci, check_ofs);
+ } else if (p_item->cells[i].checked) {
checked->draw(ci, check_ofs);
} else {
unchecked->draw(ci, check_ofs);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index be711b4c4f..c207737cc0 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -82,6 +82,7 @@ private:
int icon_max_w = 0;
bool expr = false;
bool checked = false;
+ bool indeterminate = false;
bool editable = false;
bool selected = false;
bool selectable = true;
@@ -209,7 +210,9 @@ public:
/* check mode */
void set_checked(int p_column, bool p_checked);
+ void set_indeterminate(int p_column, bool p_indeterminate);
bool is_checked(int p_column) const;
+ bool is_indeterminate(int p_column) const;
void set_text(int p_column, String p_text);
String get_text(int p_column) const;
@@ -491,6 +494,7 @@ private:
Ref<Texture2D> checked;
Ref<Texture2D> unchecked;
+ Ref<Texture2D> indeterminate;
Ref<Texture2D> arrow_collapsed;
Ref<Texture2D> arrow;
Ref<Texture2D> select_arrow;
diff --git a/scene/main/node.h b/scene/main/node.h
index 9997f4e055..6616524866 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -202,7 +202,7 @@ protected:
static String _get_name_num_separator();
friend class SceneState;
- friend class MultiplayerAPI;
+ friend class MultiplayerReplicator;
void _add_child_nocheck(Node *p_child, const StringName &p_name);
void _set_owner_nocheck(Node *p_owner);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 6995c77b8e..73eec73d75 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -300,7 +300,7 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) {
Node *child = p_node->get_child(i);
Window *window = Object::cast_to<Window>(child);
if (window) {
- break;
+ continue;
}
_propagate_window_notification(child, p_notification);
}
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 8208c55801..5f70a31844 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -704,6 +704,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("checked", "Tree", make_icon(checked_png));
theme->set_icon("unchecked", "Tree", make_icon(unchecked_png));
+ theme->set_icon("indeterminate", "Tree", make_icon(indeterminate_png));
theme->set_icon("updown", "Tree", make_icon(updown_png));
theme->set_icon("select_arrow", "Tree", make_icon(dropdown_png));
theme->set_icon("arrow", "Tree", make_icon(arrow_down_png));
diff --git a/scene/resources/default_theme/indeterminate.png b/scene/resources/default_theme/indeterminate.png
new file mode 100644
index 0000000000..28a457b251
--- /dev/null
+++ b/scene/resources/default_theme/indeterminate.png
Binary files differ
diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h
index 4a53926066..6a556c1112 100644
--- a/scene/resources/default_theme/theme_data.h
+++ b/scene/resources/default_theme/theme_data.h
@@ -214,6 +214,14 @@ static const unsigned char icon_zoom_reset_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0xa, 0x3c, 0xc, 0x7b, 0xf0, 0xff, 0xc1, 0x7f, 0x9c, 0x22, 0xcf, 0x44, 0x1e, 0xbc, 0x84, 0x72, 0xb1, 0x8b, 0x3c, 0x58, 0x5, 0xe4, 0x40, 0xb8, 0x38, 0x45, 0x18, 0x60, 0x5c, 0x84, 0x30, 0x59, 0xa, 0xa0, 0x80, 0x6e, 0xa, 0x86, 0x92, 0x2f, 0x8, 0x3, 0x0, 0x69, 0xc8, 0x86, 0x87, 0x72, 0xca, 0x85, 0x23, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
+static const unsigned char indeterminate_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x36, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x38, 0x37, 0x40, 0x20, 0x20, 0x24, 0x20, 0x20, 0x24, 0x38, 0x36, 0x40, 0x20, 0x20, 0x25, 0x1e, 0x1e, 0x22, 0x1f, 0x1f, 0x23, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0xfe, 0xfe, 0xfe, 0x98, 0x4d, 0x2d, 0x9a, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xfa, 0xfb, 0xb4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x77, 0x5e, 0x7f, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xc1, 0x12, 0x80, 0x20, 0x8, 0x45, 0xd1, 0x4, 0x14, 0xd, 0xc5, 0xfa, 0xff, 0x9f, 0x8d, 0x9c, 0x6c, 0x9a, 0xb4, 0xe9, 0x2e, 0xcf, 0x42, 0x9e, 0xcb, 0x32, 0xe4, 0x0, 0xc9, 0xb7, 0x8, 0xc1, 0x19, 0x40, 0x60, 0xc9, 0x2d, 0xe1, 0x0, 0x6, 0xc8, 0x45, 0x6b, 0x4b, 0xb, 0xa3, 0x1, 0x89, 0x6e, 0x57, 0x2a, 0x64, 0xe0, 0x73, 0xed, 0x50, 0xb3, 0x9f, 0xc3, 0x7e, 0xf7, 0x5, 0x7f, 0x6f, 0xc, 0x67, 0x9f, 0xc3, 0xe2, 0x39, 0xc, 0x52, 0xec, 0xd3, 0xd7, 0x4, 0xb3, 0xcf, 0xbd, 0x3a, 0x0, 0xa0, 0xa2, 0x8, 0xbc, 0xf6, 0x84, 0x3a, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+static const unsigned char indeterminate_disabled_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x58, 0x58, 0x5b, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0x9e, 0x9e, 0x9e, 0x8c, 0x93, 0x80, 0x95, 0x0, 0x0, 0x0, 0x14, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xb4, 0xfa, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xb9, 0x8d, 0x1e, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xd1, 0xe, 0x80, 0x20, 0x8, 0x85, 0xe1, 0x4, 0xd4, 0x30, 0x51, 0xb6, 0xde, 0xff, 0x5d, 0x23, 0x97, 0xad, 0xa5, 0xad, 0xff, 0xf2, 0xbb, 0x90, 0xe3, 0xb2, 0xc, 0x39, 0x40, 0xf2, 0x2d, 0x42, 0x70, 0x6, 0x10, 0x58, 0x6b, 0x4b, 0x39, 0x80, 0x1, 0x72, 0x91, 0xdc, 0x92, 0xc2, 0x68, 0x40, 0x2a, 0xdb, 0x95, 0x28, 0x19, 0xf8, 0x9a, 0x3b, 0xe4, 0xea, 0xe7, 0xb0, 0xdf, 0x7d, 0xc1, 0xdf, 0x1b, 0xc3, 0xd9, 0xe7, 0xb0, 0x74, 0xe, 0x83, 0x98, 0xfa, 0xf4, 0x35, 0xc2, 0xec, 0x73, 0xaf, 0xe, 0x57, 0x20, 0x8, 0x2c, 0x1a, 0x56, 0xe5, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
static const unsigned char line_edit_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x4, 0x3, 0x0, 0x0, 0x0, 0x7f, 0x1c, 0xd2, 0x8e, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x17, 0x16, 0x1a, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x21, 0x1f, 0x25, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x1d, 0x1c, 0x21, 0x1d, 0x1c, 0x21, 0x24, 0x22, 0x29, 0x28, 0x26, 0x2d, 0x28, 0x26, 0x2e, 0x2b, 0x2a, 0x31, 0x2c, 0x2a, 0x32, 0xff, 0xff, 0xff, 0xb9, 0x11, 0x56, 0x3e, 0x0, 0x0, 0x0, 0x8, 0x74, 0x52, 0x4e, 0x53, 0x6f, 0xef, 0xf7, 0xf7, 0xf0, 0xf9, 0xf1, 0xee, 0xcf, 0x21, 0xd2, 0xdf, 0x0, 0x0, 0x0, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0x54, 0x36, 0x36, 0x12, 0x60, 0xf0, 0x98, 0xb5, 0x6a, 0x65, 0xb, 0x43, 0xe4, 0x9e, 0x33, 0xa7, 0xa7, 0x32, 0x58, 0x9d, 0x39, 0x73, 0x66, 0x31, 0x16, 0x12, 0x22, 0xb, 0x52, 0xd9, 0xc6, 0xc0, 0x2, 0xd4, 0x55, 0x0, 0x0, 0xc, 0x14, 0x1a, 0x90, 0x55, 0x1a, 0xec, 0xdb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h
index ac7a500f73..1e1821024e 100644
--- a/scene/resources/particles_material.h
+++ b/scene/resources/particles_material.h
@@ -61,6 +61,7 @@ public:
PARAM_MAX
};
+ // When extending, make sure not to overflow the size of the MaterialKey below.
enum ParticleFlags {
PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY,
PARTICLE_FLAG_ROTATE_Y,
@@ -68,6 +69,7 @@ public:
PARTICLE_FLAG_MAX
};
+ // When extending, make sure not to overflow the size of the MaterialKey below.
enum EmissionShape {
EMISSION_SHAPE_POINT,
EMISSION_SHAPE_SPHERE,
@@ -78,6 +80,7 @@ public:
EMISSION_SHAPE_MAX
};
+ // When extending, make sure not to overflow the size of the MaterialKey below.
enum SubEmitterMode {
SUB_EMITTER_DISABLED,
SUB_EMITTER_CONSTANT,
@@ -88,11 +91,13 @@ public:
private:
union MaterialKey {
+ // The bit size of the struct must be kept below or equal to 32 bits.
+ // Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode.
struct {
uint32_t texture_mask : 16;
uint32_t texture_color : 1;
uint32_t particle_flags : 4;
- uint32_t emission_shape : 2;
+ uint32_t emission_shape : 3;
uint32_t invalid_key : 1;
uint32_t has_emission_color : 1;
uint32_t sub_emitter : 2;
diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp
index c03a92b503..6e9429034f 100644
--- a/scene/resources/skeleton_modification_2d_fabrik.cpp
+++ b/scene/resources/skeleton_modification_2d_fabrik.cpp
@@ -149,15 +149,6 @@ void SkeletonModification2DFABRIK::_execute(float p_delta) {
return;
}
fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform();
-
- // Apply magnet positions
- if (i == 0) {
- continue; // The origin cannot use a magnet position!
- } else {
- Transform2D joint_trans = fabrik_transform_chain[i];
- joint_trans.set_origin(joint_trans.get_origin() + fabrik_data_chain[i].magnet_position);
- fabrik_transform_chain.write[i] = joint_trans;
- }
}
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache));
@@ -223,6 +214,11 @@ void SkeletonModification2DFABRIK::chain_backwards() {
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache));
Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index];
+ // Apply magnet position
+ if (final_joint_index != 0) {
+ final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position);
+ }
+
// Set the rotation of the tip bone
final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin());
@@ -245,6 +241,11 @@ void SkeletonModification2DFABRIK::chain_backwards() {
Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
Transform2D current_pose = fabrik_transform_chain[i];
+ // Apply magnet position
+ if (i != 0) {
+ current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position);
+ }
+
float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y);
float length = current_bone2d_node_length / (previous_pose.get_origin() - current_pose.get_origin()).length();
Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length);
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index e4a731c7f7..e49d883ba4 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -1350,40 +1350,36 @@ void Theme::clear() {
_emit_theme_changed();
}
-void Theme::copy_default_theme() {
- Ref<Theme> default_theme2 = get_default();
- copy_theme(default_theme2);
-}
-
-void Theme::copy_theme(const Ref<Theme> &p_other) {
+void Theme::merge_with(const Ref<Theme> &p_other) {
if (p_other.is_null()) {
- clear();
return;
}
_freeze_change_propagation();
- // These items need reconnecting, so add them normally.
+ // Colors.
{
const StringName *K = nullptr;
- while ((K = p_other->icon_map.next(K))) {
+ while ((K = p_other->color_map.next(K))) {
const StringName *L = nullptr;
- while ((L = p_other->icon_map[*K].next(L))) {
- set_icon(*L, *K, p_other->icon_map[*K][*L]);
+ while ((L = p_other->color_map[*K].next(L))) {
+ set_color(*L, *K, p_other->color_map[*K][*L]);
}
}
}
+ // Constants.
{
const StringName *K = nullptr;
- while ((K = p_other->style_map.next(K))) {
+ while ((K = p_other->constant_map.next(K))) {
const StringName *L = nullptr;
- while ((L = p_other->style_map[*K].next(L))) {
- set_stylebox(*L, *K, p_other->style_map[*K][*L]);
+ while ((L = p_other->constant_map[*K].next(L))) {
+ set_constant(*L, *K, p_other->constant_map[*K][*L]);
}
}
}
+ // Fonts.
{
const StringName *K = nullptr;
while ((K = p_other->font_map.next(K))) {
@@ -1394,13 +1390,46 @@ void Theme::copy_theme(const Ref<Theme> &p_other) {
}
}
- // These items can be simply copied.
- font_size_map = p_other->font_size_map;
- color_map = p_other->color_map;
- constant_map = p_other->constant_map;
+ // Font sizes.
+ {
+ const StringName *K = nullptr;
+ while ((K = p_other->font_size_map.next(K))) {
+ const StringName *L = nullptr;
+ while ((L = p_other->font_size_map[*K].next(L))) {
+ set_font_size(*L, *K, p_other->font_size_map[*K][*L]);
+ }
+ }
+ }
- variation_map = p_other->variation_map;
- variation_base_map = p_other->variation_base_map;
+ // Icons.
+ {
+ const StringName *K = nullptr;
+ while ((K = p_other->icon_map.next(K))) {
+ const StringName *L = nullptr;
+ while ((L = p_other->icon_map[*K].next(L))) {
+ set_icon(*L, *K, p_other->icon_map[*K][*L]);
+ }
+ }
+ }
+
+ // Styleboxes.
+ {
+ const StringName *K = nullptr;
+ while ((K = p_other->style_map.next(K))) {
+ const StringName *L = nullptr;
+ while ((L = p_other->style_map[*K].next(L))) {
+ set_stylebox(*L, *K, p_other->style_map[*K][*L]);
+ }
+ }
+ }
+
+ // Type variations.
+ {
+ const StringName *K = nullptr;
+ while ((K = p_other->variation_map.next(K))) {
+ set_type_variation(*K, p_other->variation_map[*K]);
+ }
+ }
_unfreeze_and_propagate_changes();
}
@@ -1534,8 +1563,6 @@ void Theme::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list);
ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list);
- ClassDB::bind_method(D_METHOD("clear"), &Theme::clear);
-
ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font);
ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font);
@@ -1558,8 +1585,8 @@ void Theme::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list);
- ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme);
- ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme);
+ ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with);
+ ClassDB::bind_method(D_METHOD("clear"), &Theme::clear);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font");
ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size");
diff --git a/scene/resources/theme.h b/scene/resources/theme.h
index 8a8fc28be1..15f21b91b8 100644
--- a/scene/resources/theme.h
+++ b/scene/resources/theme.h
@@ -210,8 +210,7 @@ public:
void get_type_list(List<StringName> *p_list) const;
void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list);
- void copy_default_theme();
- void copy_theme(const Ref<Theme> &p_other);
+ void merge_with(const Ref<Theme> &p_other);
void clear();
Theme();
diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp
index f5b6be4795..0c191fe2f7 100644
--- a/servers/rendering/renderer_rd/effects_rd.cpp
+++ b/servers/rendering/renderer_rd/effects_rd.cpp
@@ -1914,7 +1914,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
}
}
- { // Initialize copy
+ if (!prefer_raster_effects) { // Initialize copy
Vector<String> copy_modes;
copy_modes.push_back("\n#define MODE_GAUSSIAN_BLUR\n");
copy_modes.push_back("\n#define MODE_GAUSSIAN_BLUR\n#define DST_IMAGE_8BIT\n");
@@ -2113,7 +2113,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
}
}
- {
+ if (!prefer_raster_effects) {
// Initialize ssao
RD::SamplerState sampler;
@@ -2257,7 +2257,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
ERR_FAIL_COND(pipeline != SSAO_MAX);
}
- {
+ if (!prefer_raster_effects) {
// Initialize roughness limiter
Vector<String> shader_modes;
shader_modes.push_back("");
@@ -2355,7 +2355,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
}
}
- {
+ if (!prefer_raster_effects) {
Vector<String> specular_modes;
specular_modes.push_back("\n#define MODE_MERGE\n");
specular_modes.push_back("\n#define MODE_MERGE\n#define MODE_SSR\n");
@@ -2391,72 +2391,74 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
}
}
- {
- Vector<String> ssr_modes;
- ssr_modes.push_back("\n");
- ssr_modes.push_back("\n#define MODE_ROUGH\n");
+ if (!prefer_raster_effects) {
+ {
+ Vector<String> ssr_modes;
+ ssr_modes.push_back("\n");
+ ssr_modes.push_back("\n#define MODE_ROUGH\n");
- ssr.shader.initialize(ssr_modes);
+ ssr.shader.initialize(ssr_modes);
- ssr.shader_version = ssr.shader.version_create();
+ ssr.shader_version = ssr.shader.version_create();
- for (int i = 0; i < SCREEN_SPACE_REFLECTION_MAX; i++) {
- ssr.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr.shader.version_get_shader(ssr.shader_version, i));
+ for (int i = 0; i < SCREEN_SPACE_REFLECTION_MAX; i++) {
+ ssr.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr.shader.version_get_shader(ssr.shader_version, i));
+ }
}
- }
- {
- Vector<String> ssr_filter_modes;
- ssr_filter_modes.push_back("\n");
- ssr_filter_modes.push_back("\n#define VERTICAL_PASS\n");
+ {
+ Vector<String> ssr_filter_modes;
+ ssr_filter_modes.push_back("\n");
+ ssr_filter_modes.push_back("\n#define VERTICAL_PASS\n");
- ssr_filter.shader.initialize(ssr_filter_modes);
+ ssr_filter.shader.initialize(ssr_filter_modes);
- ssr_filter.shader_version = ssr_filter.shader.version_create();
+ ssr_filter.shader_version = ssr_filter.shader.version_create();
- for (int i = 0; i < SCREEN_SPACE_REFLECTION_FILTER_MAX; i++) {
- ssr_filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr_filter.shader.version_get_shader(ssr_filter.shader_version, i));
+ for (int i = 0; i < SCREEN_SPACE_REFLECTION_FILTER_MAX; i++) {
+ ssr_filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr_filter.shader.version_get_shader(ssr_filter.shader_version, i));
+ }
}
- }
- {
- Vector<String> ssr_scale_modes;
- ssr_scale_modes.push_back("\n");
+ {
+ Vector<String> ssr_scale_modes;
+ ssr_scale_modes.push_back("\n");
- ssr_scale.shader.initialize(ssr_scale_modes);
+ ssr_scale.shader.initialize(ssr_scale_modes);
- ssr_scale.shader_version = ssr_scale.shader.version_create();
+ ssr_scale.shader_version = ssr_scale.shader.version_create();
- ssr_scale.pipeline = RD::get_singleton()->compute_pipeline_create(ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0));
- }
+ ssr_scale.pipeline = RD::get_singleton()->compute_pipeline_create(ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0));
+ }
- {
- Vector<String> sss_modes;
- sss_modes.push_back("\n#define USE_11_SAMPLES\n");
- sss_modes.push_back("\n#define USE_17_SAMPLES\n");
- sss_modes.push_back("\n#define USE_25_SAMPLES\n");
+ {
+ Vector<String> sss_modes;
+ sss_modes.push_back("\n#define USE_11_SAMPLES\n");
+ sss_modes.push_back("\n#define USE_17_SAMPLES\n");
+ sss_modes.push_back("\n#define USE_25_SAMPLES\n");
- sss.shader.initialize(sss_modes);
+ sss.shader.initialize(sss_modes);
- sss.shader_version = sss.shader.version_create();
+ sss.shader_version = sss.shader.version_create();
- for (int i = 0; i < sss_modes.size(); i++) {
- sss.pipelines[i] = RD::get_singleton()->compute_pipeline_create(sss.shader.version_get_shader(sss.shader_version, i));
+ for (int i = 0; i < sss_modes.size(); i++) {
+ sss.pipelines[i] = RD::get_singleton()->compute_pipeline_create(sss.shader.version_get_shader(sss.shader_version, i));
+ }
}
- }
- {
- Vector<String> resolve_modes;
- resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n");
- resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n#define VOXEL_GI_RESOLVE\n");
- resolve_modes.push_back("\n#define MODE_RESOLVE_DEPTH\n");
+ {
+ Vector<String> resolve_modes;
+ resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n");
+ resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n#define VOXEL_GI_RESOLVE\n");
+ resolve_modes.push_back("\n#define MODE_RESOLVE_DEPTH\n");
- resolve.shader.initialize(resolve_modes);
+ resolve.shader.initialize(resolve_modes);
- resolve.shader_version = resolve.shader.version_create();
+ resolve.shader_version = resolve.shader.version_create();
- for (int i = 0; i < RESOLVE_MODE_MAX; i++) {
- resolve.pipelines[i] = RD::get_singleton()->compute_pipeline_create(resolve.shader.version_get_shader(resolve.shader_version, i));
+ for (int i = 0; i < RESOLVE_MODE_MAX; i++) {
+ resolve.pipelines[i] = RD::get_singleton()->compute_pipeline_create(resolve.shader.version_get_shader(resolve.shader_version, i));
+ }
}
}
@@ -2522,10 +2524,6 @@ EffectsRD::~EffectsRD() {
RD::get_singleton()->free(index_buffer); //array gets freed as dependency
RD::get_singleton()->free(filter.coefficient_buffer);
- RD::get_singleton()->free(ssao.mirror_sampler);
- RD::get_singleton()->free(ssao.gather_constants_buffer);
- RD::get_singleton()->free(ssao.importance_map_load_counter);
-
if (prefer_raster_effects) {
blur_raster.shader.version_free(blur_raster.shader_version);
bokeh.raster_shader.version_free(blur_raster.shader_version);
@@ -2540,21 +2538,27 @@ EffectsRD::~EffectsRD() {
cubemap_downsampler.compute_shader.version_free(cubemap_downsampler.shader_version);
filter.compute_shader.version_free(filter.shader_version);
}
- copy.shader.version_free(copy.shader_version);
+ if (!prefer_raster_effects) {
+ copy.shader.version_free(copy.shader_version);
+ resolve.shader.version_free(resolve.shader_version);
+ specular_merge.shader.version_free(specular_merge.shader_version);
+ ssao.blur_shader.version_free(ssao.blur_shader_version);
+ ssao.gather_shader.version_free(ssao.gather_shader_version);
+ ssao.downsample_shader.version_free(ssao.downsample_shader_version);
+ ssao.interleave_shader.version_free(ssao.interleave_shader_version);
+ ssao.importance_map_shader.version_free(ssao.importance_map_shader_version);
+ roughness_limiter.shader.version_free(roughness_limiter.shader_version);
+ ssr.shader.version_free(ssr.shader_version);
+ ssr_filter.shader.version_free(ssr_filter.shader_version);
+ ssr_scale.shader.version_free(ssr_scale.shader_version);
+ sss.shader.version_free(sss.shader_version);
+
+ RD::get_singleton()->free(ssao.mirror_sampler);
+ RD::get_singleton()->free(ssao.gather_constants_buffer);
+ RD::get_singleton()->free(ssao.importance_map_load_counter);
+ }
copy_to_fb.shader.version_free(copy_to_fb.shader_version);
cube_to_dp.shader.version_free(cube_to_dp.shader_version);
- resolve.shader.version_free(resolve.shader_version);
- roughness_limiter.shader.version_free(roughness_limiter.shader_version);
sort.shader.version_free(sort.shader_version);
- specular_merge.shader.version_free(specular_merge.shader_version);
- ssao.blur_shader.version_free(ssao.blur_shader_version);
- ssao.gather_shader.version_free(ssao.gather_shader_version);
- ssao.downsample_shader.version_free(ssao.downsample_shader_version);
- ssao.interleave_shader.version_free(ssao.interleave_shader_version);
- ssao.importance_map_shader.version_free(ssao.importance_map_shader_version);
- ssr.shader.version_free(ssr.shader_version);
- ssr_filter.shader.version_free(ssr_filter.shader_version);
- ssr_scale.shader.version_free(ssr_scale.shader_version);
- sss.shader.version_free(sss.shader_version);
tonemap.shader.version_free(tonemap.shader_version);
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 5a0ad97c7c..611f7c6494 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -3062,7 +3062,7 @@ RenderForwardClustered::RenderForwardClustered(RendererStorageRD *p_storage) :
defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n";
}
defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n";
- defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n";
+ defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
{
//lightmaps
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 96b3cdadd4..2064d9c5c5 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -2630,7 +2630,7 @@ RenderForwardMobile::RenderForwardMobile(RendererStorageRD *p_storage) :
defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n";
}
// defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n";
- defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n";
+ defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
{
//lightmaps
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 02d548bf13..62e9386f95 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -281,6 +281,8 @@ RendererCompositorRD::RendererCompositorRD() {
scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
}
+ scene->init();
+
// now we're ready to create our effects,
storage->init_effects(!scene->_render_buffers_can_be_storage());
}
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
index 98d08f68e8..098e2a5c87 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
@@ -2812,8 +2812,6 @@ void RendererSceneGIRD::init(RendererStorageRD *p_storage, RendererSceneSkyRD *p
{
//kinda complicated to compute the amount of slots, we try to use as many as we can
- voxel_gi_max_lights = 32;
-
voxel_gi_lights = memnew_arr(VoxelGILight, voxel_gi_max_lights);
voxel_gi_lights_uniform = RD::get_singleton()->uniform_buffer_create(voxel_gi_max_lights * sizeof(VoxelGILight));
voxel_gi_quality = RS::VoxelGIQuality(CLAMP(int(GLOBAL_GET("rendering/global_illumination/voxel_gi/quality")), 0, 1));
@@ -3009,7 +3007,9 @@ void RendererSceneGIRD::free() {
sdfgi_shader.integrate.version_free(sdfgi_shader.integrate_shader);
sdfgi_shader.preprocess.version_free(sdfgi_shader.preprocess_shader);
- memdelete_arr(voxel_gi_lights);
+ if (voxel_gi_lights) {
+ memdelete_arr(voxel_gi_lights);
+ }
}
RendererSceneGIRD::SDFGI *RendererSceneGIRD::create_sdfgi(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world_position, uint32_t p_requested_history_size) {
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
index 128bf09063..0b4622646f 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
@@ -110,8 +110,8 @@ private:
float pad[3];
};
- VoxelGILight *voxel_gi_lights;
- uint32_t voxel_gi_max_lights;
+ VoxelGILight *voxel_gi_lights = nullptr;
+ uint32_t voxel_gi_max_lights = 32;
RID voxel_gi_lights_uniform;
enum {
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 5016c99a1e..d81c216f37 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -4066,7 +4066,7 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const CameraData
render_state.voxel_gi_count = 0;
- if (rb != nullptr) {
+ if (rb != nullptr && is_dynamic_gi_supported()) {
if (rb->sdfgi) {
rb->sdfgi->update_cascades();
rb->sdfgi->pre_process_gi(render_data.cam_transform, &render_data, this);
@@ -4587,10 +4587,12 @@ uint32_t RendererSceneRenderRD::get_max_elements() const {
}
RendererSceneRenderRD::RendererSceneRenderRD(RendererStorageRD *p_storage) {
- max_cluster_elements = get_max_elements();
-
storage = p_storage;
singleton = this;
+}
+
+void RendererSceneRenderRD::init() {
+ max_cluster_elements = get_max_elements();
directional_shadow.size = GLOBAL_GET("rendering/shadows/directional_shadow/size");
directional_shadow.use_16_bits = GLOBAL_GET("rendering/shadows/directional_shadow/16_bits");
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
index 5306df36f0..a7cc27be4a 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
@@ -1290,6 +1290,8 @@ public:
virtual bool is_volumetric_supported() const;
virtual uint32_t get_max_elements() const;
+ void init();
+
RendererSceneRenderRD(RendererStorageRD *p_storage);
~RendererSceneRenderRD();
};
diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.cpp b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
index 7d438345f1..8cc20618fc 100644
--- a/servers/rendering/renderer_rd/renderer_storage_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
@@ -9310,15 +9310,6 @@ RendererStorageRD::RendererStorageRD() {
}
}
- {
- Vector<String> sdf_versions;
- sdf_versions.push_back(""); //one only
- voxel_gi_sdf_shader.initialize(sdf_versions);
- voxel_gi_sdf_shader_version = voxel_gi_sdf_shader.version_create();
- voxel_gi_sdf_shader_version_shader = voxel_gi_sdf_shader.version_get_shader(voxel_gi_sdf_shader_version, 0);
- voxel_gi_sdf_shader_pipeline = RD::get_singleton()->compute_pipeline_create(voxel_gi_sdf_shader_version_shader);
- }
-
using_lightmap_array = true; // high end
if (using_lightmap_array) {
uint32_t textures_per_stage = RD::get_singleton()->limit_get(RD::LIMIT_MAX_TEXTURES_PER_SHADER_STAGE);
@@ -9538,7 +9529,6 @@ RendererStorageRD::~RendererStorageRD() {
RD::get_singleton()->free(mesh_default_rd_buffers[i]);
}
- voxel_gi_sdf_shader.version_free(voxel_gi_sdf_shader_version);
particles_shader.copy_shader.version_free(particles_shader.copy_shader_version);
rt_sdf.shader.version_free(rt_sdf.shader_version);
diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.h b/servers/rendering/renderer_rd/renderer_storage_rd.h
index c048be5dc8..02395a884f 100644
--- a/servers/rendering/renderer_rd/renderer_storage_rd.h
+++ b/servers/rendering/renderer_rd/renderer_storage_rd.h
@@ -668,14 +668,14 @@ private:
};
uint32_t emitting;
- double system_phase;
- double prev_system_phase;
+ float system_phase;
+ float prev_system_phase;
uint32_t cycle;
real_t explosiveness;
real_t randomness;
- double time;
- double delta;
+ float time;
+ float delta;
uint32_t frame;
uint32_t pad0;
@@ -812,7 +812,7 @@ private:
struct ParticlesShader {
struct PushConstant {
- double lifetime;
+ float lifetime;
uint32_t clear;
uint32_t total_particles;
uint32_t trail_size;
@@ -1100,11 +1100,6 @@ private:
Dependency dependency;
};
- VoxelGiSdfShaderRD voxel_gi_sdf_shader;
- RID voxel_gi_sdf_shader_version;
- RID voxel_gi_sdf_shader_version_shader;
- RID voxel_gi_sdf_shader_pipeline;
-
mutable RID_Owner<VoxelGI, true> voxel_gi_owner;
/* REFLECTION PROBE */
diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp
index 5bb12fc168..82efa1318c 100644
--- a/servers/rendering/renderer_rd/shader_rd.cpp
+++ b/servers/rendering/renderer_rd/shader_rd.cpp
@@ -279,7 +279,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, Version *p_version) {
return;
}
- Vector<uint8_t> shader_data = RD::get_singleton()->shader_compile_binary_from_spirv(stages);
+ Vector<uint8_t> shader_data = RD::get_singleton()->shader_compile_binary_from_spirv(stages, name + ":" + itos(p_variant));
ERR_FAIL_COND(shader_data.size() == 0);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index bb9ffffe8a..b302c6b793 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -74,8 +74,8 @@ String RenderingDevice::shader_get_spirv_cache_key() const {
return String();
}
-RID RenderingDevice::shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) {
- Vector<uint8_t> bytecode = shader_compile_binary_from_spirv(p_spirv);
+RID RenderingDevice::shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name) {
+ Vector<uint8_t> bytecode = shader_compile_binary_from_spirv(p_spirv, p_shader_name);
ERR_FAIL_COND_V(bytecode.size() == 0, RID());
return shader_create_from_bytecode(bytecode);
}
@@ -192,7 +192,7 @@ Ref<RDShaderSPIRV> RenderingDevice::_shader_compile_spirv_from_source(const Ref<
return bytecode;
}
-Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_spirv) {
+Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name) {
ERR_FAIL_COND_V(p_spirv.is_null(), Vector<uint8_t>());
Vector<ShaderStageSPIRVData> stage_data;
@@ -209,10 +209,10 @@ Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDS
stage_data.push_back(sd);
}
- return shader_compile_binary_from_spirv(stage_data);
+ return shader_compile_binary_from_spirv(stage_data, p_shader_name);
}
-RID RenderingDevice::_shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv) {
+RID RenderingDevice::_shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name) {
ERR_FAIL_COND_V(p_spirv.is_null(), RID());
Vector<ShaderStageSPIRVData> stage_data;
@@ -392,8 +392,8 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("index_array_create", "index_buffer", "index_offset", "index_count"), &RenderingDevice::index_array_create);
ClassDB::bind_method(D_METHOD("shader_compile_spirv_from_source", "shader_source", "allow_cache"), &RenderingDevice::_shader_compile_spirv_from_source, DEFVAL(true));
- ClassDB::bind_method(D_METHOD("shader_compile_binary_from_spirv", "spirv_data"), &RenderingDevice::_shader_compile_binary_from_spirv);
- ClassDB::bind_method(D_METHOD("shader_create_from_spirv", "spirv_data"), &RenderingDevice::_shader_compile_binary_from_spirv);
+ ClassDB::bind_method(D_METHOD("shader_compile_binary_from_spirv", "spirv_data", "name"), &RenderingDevice::_shader_compile_binary_from_spirv, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("shader_create_from_spirv", "spirv_data", "name"), &RenderingDevice::_shader_create_from_spirv, DEFVAL(""));
ClassDB::bind_method(D_METHOD("shader_create_from_bytecode", "binary_data"), &RenderingDevice::shader_create_from_bytecode);
ClassDB::bind_method(D_METHOD("shader_get_vertex_input_attribute_mask", "shader"), &RenderingDevice::shader_get_vertex_input_attribute_mask);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index bb028cb84c..e2d207dab2 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -668,9 +668,9 @@ public:
};
virtual String shader_get_binary_cache_key() const = 0;
- virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) = 0;
+ virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = "") = 0;
- virtual RID shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv);
+ virtual RID shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = "");
virtual RID shader_create_from_bytecode(const Vector<uint8_t> &p_shader_binary) = 0;
virtual uint32_t shader_get_vertex_input_attribute_mask(RID p_shader) = 0;
@@ -1200,8 +1200,8 @@ protected:
RID _vertex_array_create(uint32_t p_vertex_count, VertexFormatID p_vertex_format, const TypedArray<RID> &p_src_buffers);
Ref<RDShaderSPIRV> _shader_compile_spirv_from_source(const Ref<RDShaderSource> &p_source, bool p_allow_cache = true);
- Vector<uint8_t> _shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_bytecode);
- RID _shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv);
+ Vector<uint8_t> _shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_bytecode, const String &p_shader_name = "");
+ RID _shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name = "");
RID _uniform_set_create(const Array &p_uniforms, RID p_shader, uint32_t p_shader_set);
diff --git a/tests/test_geometry_3d.h b/tests/test_geometry_3d.h
index 2b2a424b2b..40cb8bc07a 100644
--- a/tests/test_geometry_3d.h
+++ b/tests/test_geometry_3d.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef TEST_3D_GEOMETRY_H
-#define TEST_3D_GEOMETRY_H
+#ifndef TEST_GEOMETRY_3D_H
+#define TEST_GEOMETRY_3D_H
#include "core/math/geometry_3d.h"
#include "core/math/plane.h"
@@ -38,7 +38,7 @@
#include "tests/test_macros.h"
#include "vector"
-namespace Test3DGeometry {
+namespace TestGeometry3D {
TEST_CASE("[Geometry3D] Closest Points Between Segments") {
struct Case {
Vector3 p_1, p_2, p_3, p_4;
@@ -57,6 +57,7 @@ TEST_CASE("[Geometry3D] Closest Points Between Segments") {
CHECK(current_case.got_2.is_equal_approx(current_case.want_2));
}
}
+
TEST_CASE("[Geometry3D] Closest Distance Between Segments") {
struct Case {
Vector3 p_1, p_2, p_3, p_4;
@@ -73,6 +74,7 @@ TEST_CASE("[Geometry3D] Closest Distance Between Segments") {
CHECK(out == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Build Box Planes") {
const Vector3 extents = Vector3(5, 5, 20);
Vector<Plane> box = Geometry3D::build_box_planes(extents);
@@ -90,6 +92,7 @@ TEST_CASE("[Geometry3D] Build Box Planes") {
CHECK(extents.z == box[5].d);
CHECK(box[5].normal == Vector3(0, 0, -1));
}
+
TEST_CASE("[Geometry3D] Build Capsule Planes") {
struct Case {
real_t radius, height;
@@ -109,6 +112,7 @@ TEST_CASE("[Geometry3D] Build Capsule Planes") {
CHECK(capsule.size() == current_case.want_size);
}
}
+
TEST_CASE("[Geometry3D] Build Cylinder Planes") {
struct Case {
real_t radius, height;
@@ -127,6 +131,7 @@ TEST_CASE("[Geometry3D] Build Cylinder Planes") {
CHECK(planes.size() == current_case.want_size);
}
}
+
TEST_CASE("[Geometry3D] Build Sphere Planes") {
struct Case {
real_t radius;
@@ -145,6 +150,7 @@ TEST_CASE("[Geometry3D] Build Sphere Planes") {
CHECK(planes.size() == 63);
}
}
+
TEST_CASE("[Geometry3D] Build Convex Mesh") {
struct Case {
Vector<Plane> object;
@@ -166,6 +172,7 @@ TEST_CASE("[Geometry3D] Build Convex Mesh") {
CHECK(mesh.vertices.size() == current_case.want_vertices);
}
}
+
TEST_CASE("[Geometry3D] Clip Polygon") {
struct Case {
Plane clipping_plane;
@@ -190,6 +197,7 @@ TEST_CASE("[Geometry3D] Clip Polygon") {
}
}
}
+
TEST_CASE("[Geometry3D] Compute Convex Mesh Points") {
struct Case {
Vector<Plane> mesh;
@@ -215,6 +223,7 @@ TEST_CASE("[Geometry3D] Compute Convex Mesh Points") {
CHECK(vectors == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
struct Case {
Vector3 point;
@@ -235,6 +244,7 @@ TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
CHECK(output.is_equal_approx(current_case.want));
}
}
+
TEST_CASE("[Geometry3D] Plane and Box Overlap") {
struct Case {
Vector3 normal, max_box;
@@ -254,6 +264,7 @@ TEST_CASE("[Geometry3D] Plane and Box Overlap") {
CHECK(overlap == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Is Point in Projected Triangle") {
struct Case {
Vector3 point, v_1, v_2, v_3;
@@ -272,6 +283,7 @@ TEST_CASE("[Geometry3D] Is Point in Projected Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") {
struct Case {
Vector3 from, direction, v_1, v_2, v_3;
@@ -291,6 +303,7 @@ TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Does Segment Intersect Convex") {
struct Case {
Vector3 from, to;
@@ -311,6 +324,7 @@ TEST_CASE("[Geometry3D] Does Segment Intersect Convex") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
struct Case {
Vector3 from, to;
@@ -330,6 +344,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
struct Case {
Vector3 from, to, sphere_pos;
@@ -350,6 +365,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Triangle") {
struct Case {
Vector3 from, to, v_1, v_2, v_3, *result;
@@ -368,6 +384,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Triangle and Box Overlap") {
struct Case {
Vector3 box_centre;
@@ -389,6 +406,7 @@ TEST_CASE("[Geometry3D] Triangle and Box Overlap") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") {
struct Case {
Vector<Vector3> triangle;
@@ -413,5 +431,6 @@ TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") {
CHECK(output == current_case.want);
}
}
-} // namespace Test3DGeometry
-#endif
+} // namespace TestGeometry3D
+
+#endif // TEST_GEOMETRY_3D_H
diff --git a/tests/test_string.h b/tests/test_string.h
index 1982d8de60..79fdb7bb56 100644
--- a/tests/test_string.h
+++ b/tests/test_string.h
@@ -350,6 +350,9 @@ TEST_CASE("[String] Insertion") {
}
TEST_CASE("[String] Number to string") {
+ CHECK(String::num(0) == "0");
+ CHECK(String::num(0.0) == "0"); // No trailing zeros.
+ CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
CHECK(String::num_real(3.141593) == "3.141593");
@@ -357,6 +360,27 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num_int64(3141593) == "3141593");
CHECK(String::num_int64(0xA141593, 16) == "a141593");
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
+ CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
+
+ // Checks doubles with many decimal places.
+ CHECK(String::num(0.0000012345432123454321, -1) == "0.00000123454321"); // -1 uses 14 as sane default.
+ CHECK(String::num(0.0000012345432123454321) == "0.00000123454321"); // -1 is the default value.
+ CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
+ CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
+ CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
+ CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
+
+ // Note: When relevant (remainder > 0.5), the last digit gets rounded up,
+ // which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
+ CHECK(String::num(0.0000056789876567898765) == "0.00000567898766"); // Should round last digit.
+ CHECK(String::num(10000.000005678999999999) == "10000.000005679"); // We cut at ...789|99 which is rounded to ...79, so only 13 decimals.
+ CHECK(String::num(42.12999999, 6) == "42.13"); // Also happens with lower decimals count.
+
+ // 32 is MAX_DECIMALS. We can't reliably store that many so we can't compare against a string,
+ // but we can check that the string length is 34 (32 + 2 for "0.").
+ CHECK(String::num(0.00000123456789987654321123456789987654321, 32).length() == 34);
+ CHECK(String::num(0.00000123456789987654321123456789987654321, 42).length() == 34); // Should enforce MAX_DECIMALS.
+ CHECK(String::num(10000.00000123456789987654321123456789987654321, 42).length() == 38); // 32 decimals + "10000.".
}
TEST_CASE("[String] String to integer") {
diff --git a/tests/test_variant.h b/tests/test_variant.h
index dfc72b512c..598fe488d7 100644
--- a/tests/test_variant.h
+++ b/tests/test_variant.h
@@ -39,7 +39,7 @@
namespace TestVariant {
TEST_CASE("[Variant] Writer and parser integer") {
- int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31,-2^31-1].
+ int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31, +2^31-1].
String a32_str;
VariantWriter::write_to_string(a32, a32_str);
@@ -76,34 +76,35 @@ TEST_CASE("[Variant] Writer and parser integer") {
CHECK_MESSAGE(b64_int_parsed == 9223372036854775807, "The result should be clamped to max value.");
}
-TEST_CASE("[Variant] Writer and parser float") {
- // Assuming real_t is double.
- real_t a64 = 340282346638528859811704183484516925440.0; // std::numeric_limits<real_t>::max()
+TEST_CASE("[Variant] Writer and parser Variant::FLOAT") {
+ // Variant::FLOAT is always 64-bit (C++ double).
+ // This is the maximum non-infinity double-precision float.
+ double a64 = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0;
String a64_str;
VariantWriter::write_to_string(a64, a64_str);
- CHECK_MESSAGE(a64_str == "3.40282e+38", "Writes in scientific notation.");
+ CHECK_MESSAGE(a64_str == "1.79769e+308", "Writes in scientific notation.");
CHECK_MESSAGE(a64_str != "inf", "Should not overflow.");
CHECK_MESSAGE(a64_str != "nan", "The result should be defined.");
- VariantParser::StreamString ss;
String errs;
int line;
- Variant b64_parsed;
- real_t b64_float_parsed;
-
- ss.s = a64_str;
- VariantParser::parse(&ss, b64_parsed, errs, line);
- b64_float_parsed = b64_parsed;
+ Variant variant_parsed;
+ double float_parsed;
- CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should parse back.");
+ VariantParser::StreamString bss;
+ bss.s = a64_str;
+ VariantParser::parse(&bss, variant_parsed, errs, line);
+ float_parsed = variant_parsed;
// Loses precision, but that's alright.
-
- ss.s = "1.0e+100"; // Float version of Googol!
- VariantParser::parse(&ss, b64_parsed, errs, line);
- b64_float_parsed = b64_parsed;
-
- CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should not overflow.");
+ CHECK_MESSAGE(float_parsed == 1.79769e+308, "Should parse back.");
+
+ // Approximation of Googol with a double-precision float.
+ VariantParser::StreamString css;
+ css.s = "1.0e+100";
+ VariantParser::parse(&css, variant_parsed, errs, line);
+ float_parsed = variant_parsed;
+ CHECK_MESSAGE(float_parsed == 1.0e+100, "Should match the double literal.");
}
TEST_CASE("[Variant] Assignment To Bool from Int,Float,String,Vec2,Vec2i,Vec3,Vec3i and Color") {