summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--SConstruct16
-rw-r--r--core/SCsub25
-rw-r--r--core/debugger/remote_debugger_peer.cpp4
-rw-r--r--core/doc_data.h55
-rw-r--r--core/io/compression.cpp218
-rw-r--r--core/io/compression.h3
-rw-r--r--core/io/file_access.cpp1
-rw-r--r--core/io/file_access.h3
-rw-r--r--core/math/a_star_grid_2d.cpp74
-rw-r--r--core/math/a_star_grid_2d.h15
-rw-r--r--core/object/class_db.cpp15
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/object/object.cpp41
-rw-r--r--core/object/object.h3
-rw-r--r--core/os/thread.cpp39
-rw-r--r--core/os/thread.h24
-rw-r--r--core/templates/hashfuncs.h6
-rw-r--r--doc/classes/AStarGrid2D.xml13
-rw-r--r--doc/classes/AnimationTrackEditPlugin.xml9
-rw-r--r--doc/classes/ColorPicker.xml3
-rw-r--r--doc/classes/Curve.xml2
-rw-r--r--doc/classes/Curve2D.xml2
-rw-r--r--doc/classes/Curve3D.xml2
-rw-r--r--doc/classes/EditorScript.xml4
-rw-r--r--doc/classes/EditorSettings.xml3
-rw-r--r--doc/classes/EditorVCSInterface.xml38
-rw-r--r--doc/classes/Engine.xml2
-rw-r--r--doc/classes/EngineDebugger.xml2
-rw-r--r--doc/classes/Expression.xml1
-rw-r--r--doc/classes/FileAccess.xml5
-rw-r--r--doc/classes/Gradient.xml3
-rw-r--r--doc/classes/HSlider.xml3
-rw-r--r--doc/classes/ImageTexture3D.xml2
-rw-r--r--doc/classes/ImageTextureLayered.xml2
-rw-r--r--doc/classes/ImmediateMesh.xml13
-rw-r--r--doc/classes/ItemList.xml3
-rw-r--r--doc/classes/LineEdit.xml6
-rw-r--r--doc/classes/Marker2D.xml2
-rw-r--r--doc/classes/MeshDataTool.xml4
-rw-r--r--doc/classes/NavigationLink2D.xml8
-rw-r--r--doc/classes/NavigationLink3D.xml8
-rw-r--r--doc/classes/NavigationServer2D.xml22
-rw-r--r--doc/classes/NavigationServer3D.xml22
-rw-r--r--doc/classes/PackedByteArray.xml2
-rw-r--r--doc/classes/PathFollow3D.xml2
-rw-r--r--doc/classes/PhysicsServer2D.xml2
-rw-r--r--doc/classes/ProjectSettings.xml9
-rw-r--r--doc/classes/Rect2.xml2
-rw-r--r--doc/classes/Rect2i.xml4
-rw-r--r--doc/classes/RenderingDevice.xml8
-rw-r--r--doc/classes/RigidBody2D.xml2
-rw-r--r--doc/classes/RigidBody3D.xml2
-rw-r--r--doc/classes/SceneTree.xml6
-rw-r--r--doc/classes/SkeletonIK3D.xml4
-rw-r--r--doc/classes/SoftBody3D.xml4
-rw-r--r--doc/classes/TextEdit.xml4
-rw-r--r--doc/classes/TileMap.xml23
-rw-r--r--doc/classes/Timer.xml3
-rw-r--r--doc/classes/Tree.xml3
-rw-r--r--doc/classes/TreeItem.xml26
-rw-r--r--doc/classes/VSlider.xml3
-rw-r--r--doc/classes/VideoStreamPlayback.xml10
-rw-r--r--doc/classes/VisualInstance3D.xml2
-rw-r--r--doc/classes/VoxelGI.xml2
-rw-r--r--doc/classes/Window.xml12
-rwxr-xr-xdoc/tools/make_rst.py21
-rw-r--r--drivers/alsa/audio_driver_alsa.cpp3
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.cpp4
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.cpp2
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp12
-rw-r--r--drivers/vulkan/rendering_device_vulkan.h1
-rw-r--r--drivers/wasapi/audio_driver_wasapi.cpp2
-rw-r--r--drivers/xaudio2/audio_driver_xaudio2.cpp3
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.cpp17
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.cpp6
-rw-r--r--editor/debugger/editor_debugger_node.cpp16
-rw-r--r--editor/doc_tools.cpp5
-rw-r--r--editor/editor_data.cpp10
-rw-r--r--editor/editor_data.h3
-rw-r--r--editor/editor_help.cpp154
-rw-r--r--editor/editor_help.h4
-rw-r--r--editor/editor_interface.cpp17
-rw-r--r--editor/editor_node.cpp500
-rw-r--r--editor/editor_node.h59
-rw-r--r--editor/editor_plugin.cpp4
-rw-r--r--editor/editor_plugin_settings.cpp1
-rw-r--r--editor/editor_properties_array_dict.cpp13
-rw-r--r--editor/editor_run.h1
-rw-r--r--editor/editor_run_native.cpp7
-rw-r--r--editor/editor_run_native.h6
-rw-r--r--editor/editor_script.cpp (renamed from editor/editor_run_script.cpp)33
-rw-r--r--editor/editor_script.h (renamed from editor/editor_run_script.h)21
-rw-r--r--editor/editor_settings.cpp1
-rw-r--r--editor/editor_settings.h3
-rw-r--r--editor/editor_themes.cpp3
-rw-r--r--editor/export/editor_export_platform.cpp1
-rw-r--r--editor/gui/editor_run_bar.cpp442
-rw-r--r--editor/gui/editor_run_bar.h115
-rw-r--r--editor/gui/editor_spin_slider.cpp9
-rw-r--r--editor/gui/editor_spin_slider.h1
-rw-r--r--editor/import/post_import_plugin_skeleton_rest_fixer.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp5
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.cpp101
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.h5
-rw-r--r--editor/plugins/curve_editor_plugin.cpp1
-rw-r--r--editor/plugins/curve_editor_plugin.h2
-rw-r--r--editor/plugins/material_editor_plugin.cpp1
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp5
-rw-r--r--editor/plugins/script_editor_plugin.cpp13
-rw-r--r--editor/plugins/script_text_editor.cpp1
-rw-r--r--editor/project_settings_editor.cpp1
-rw-r--r--editor/register_editor_types.cpp8
-rw-r--r--editor/scene_tree_dock.cpp120
-rw-r--r--editor/scene_tree_dock.h5
-rw-r--r--main/main.cpp34
-rw-r--r--main/main.h2
-rw-r--r--modules/csg/csg.cpp80
-rw-r--r--modules/csg/csg.h13
-rw-r--r--modules/csg/csg_shape.cpp2
-rw-r--r--modules/csg/doc_classes/CSGShape3D.xml10
-rw-r--r--modules/enet/doc_classes/ENetConnection.xml10
-rw-r--r--modules/enet/doc_classes/ENetMultiplayerPeer.xml12
-rw-r--r--modules/enet/doc_classes/ENetPacketPeer.xml10
-rw-r--r--modules/freetype/SCsub18
-rw-r--r--modules/freetype/config.py8
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml8
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp271
-rw-r--r--modules/gdscript/editor/gdscript_docgen.h42
-rw-r--r--modules/gdscript/gdscript.cpp304
-rw-r--r--modules/gdscript/gdscript.h24
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp85
-rw-r--r--modules/gdscript/gdscript_analyzer.h1
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_cache.cpp10
-rw-r--r--modules/gdscript/gdscript_cache.h3
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp312
-rw-r--r--modules/gdscript/gdscript_compiler.h3
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp4
-rw-r--r--modules/gdscript/gdscript_editor.cpp9
-rw-r--r--modules/gdscript/gdscript_function.h3
-rw-r--r--modules/gdscript/gdscript_parser.cpp103
-rw-r--r--modules/gdscript/gdscript_parser.h23
-rw-r--r--modules/gdscript/gdscript_vm.cpp8
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp26
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/assign_operator.gd31
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/assign_operator.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd15
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_constructor.gd13
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_constructor.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.gd56
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.out16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_load.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_other.out2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp1
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml10
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp5
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSpawner.xml2
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml10
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml4
-rw-r--r--modules/multiplayer/doc_classes/SceneReplicationConfig.xml16
-rw-r--r--modules/openxr/extensions/openxr_opengl_extension.cpp2
-rw-r--r--modules/upnp/doc_classes/UPNP.xml18
-rw-r--r--modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml18
-rw-r--r--modules/webrtc/doc_classes/WebRTCPeerConnection.xml16
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml2
-rw-r--r--modules/websocket/doc_classes/WebSocketPeer.xml2
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml12
-rw-r--r--platform/android/export/export_plugin.cpp4
-rw-r--r--platform/android/java_godot_lib_jni.cpp7
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml2
-rw-r--r--platform/ios/export/export_plugin.cpp15
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp1
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml2
-rw-r--r--platform/macos/os_macos.h1
-rw-r--r--platform/macos/os_macos.mm21
-rw-r--r--platform/uwp/export/export.cpp1
-rw-r--r--platform/web/audio_driver_web.cpp2
-rw-r--r--platform/web/export/export.cpp1
-rw-r--r--platform/web/export/export_plugin.cpp1
-rw-r--r--platform/windows/export/export_plugin.cpp1
-rw-r--r--scene/2d/tile_map.cpp8
-rw-r--r--scene/2d/tile_map.h4
-rw-r--r--scene/animation/animation_node_state_machine.cpp2
-rw-r--r--scene/gui/color_picker.cpp4
-rw-r--r--scene/gui/color_picker.h2
-rw-r--r--scene/gui/control.cpp13
-rw-r--r--scene/gui/item_list.cpp14
-rw-r--r--scene/gui/item_list.h4
-rw-r--r--scene/gui/slider.cpp31
-rw-r--r--scene/gui/slider.h3
-rw-r--r--scene/gui/tree.cpp162
-rw-r--r--scene/gui/tree.h29
-rw-r--r--scene/main/node.cpp16
-rw-r--r--scene/main/window.cpp4
-rw-r--r--scene/resources/default_theme/default_theme.cpp3
-rw-r--r--scene/resources/resource_format_text.cpp14
-rw-r--r--servers/audio/audio_driver_dummy.cpp2
-rw-r--r--servers/audio_server.cpp14
-rw-r--r--servers/audio_server.h5
-rw-r--r--servers/rendering/renderer_rd/environment/fog.cpp45
-rw-r--r--servers/rendering/renderer_rd/environment/fog.h23
-rw-r--r--servers/rendering/renderer_rd/environment/gi.cpp20
-rw-r--r--servers/rendering/renderer_rd/shaders/environment/gi.glsl8
-rw-r--r--servers/rendering/rendering_device.cpp1
-rw-r--r--servers/rendering/rendering_device.h1
-rw-r--r--servers/rendering/shader_preprocessor.cpp16
-rw-r--r--servers/rendering/shader_preprocessor.h4
-rw-r--r--tests/core/object/test_object.h42
223 files changed, 3275 insertions, 1680 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 008aa0db3e..9f795394ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -912,7 +912,7 @@ See the [release announcement](https://godotengine.org/article/godot-3-4-is-rele
- Fix reloading `tool` scripts in the editor ([GH-52883](https://github.com/godotengine/godot/pull/52883)).
- Fix C# bindings generator for default value types ([GH-49702](https://github.com/godotengine/godot/pull/49702)).
- Ignore paths with invalid chars in `PathWhich` ([GH-50918](https://github.com/godotengine/godot/pull/50918)).
-- Fix `List<T>` marshalling ([GH-53628](https://github.com/godotengine/godot/pull/53628)).
+- Fix `List<T>` marshaling ([GH-53628](https://github.com/godotengine/godot/pull/53628)).
- Fix `hint_string` for enum arrays ([GH-53638](https://github.com/godotengine/godot/pull/53638)).
- Keep order for C# exported members ([GH-54199](https://github.com/godotengine/godot/pull/54199)).
@@ -1276,7 +1276,7 @@ See the [release announcement](https://godotengine.org/article/godot-3-3-has-arr
#### Mono (C#)
- [Fix targeting .NETFramework with .NET 5](https://github.com/godotengine/godot/pull/44135).
-- [Fix System.Collections.Generic.List marshalling](https://github.com/godotengine/godot/pull/45029).
+- [Fix System.Collections.Generic.List marshaling](https://github.com/godotengine/godot/pull/45029).
- [Fix support for Unicode identifiers](https://github.com/godotengine/godot/pull/45310).
- [Fixes to Mono on WebAssembly](https://github.com/godotengine/godot/pull/44374).
diff --git a/SConstruct b/SConstruct
index 518dcaa05d..e5421b7887 100644
--- a/SConstruct
+++ b/SConstruct
@@ -191,6 +191,7 @@ opts.Add(BoolVariable("production", "Set defaults to build Godot for use in prod
opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True))
opts.Add(EnumVariable("precision", "Set the floating-point precision level", "single", ("single", "double")))
opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True))
+opts.Add(BoolVariable("brotli", "Enable Brotli for decompresson and WOFF2 fonts support", True))
opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False))
opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True))
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
@@ -830,6 +831,15 @@ if selected_platform in platform_list:
env.module_list = modules_enabled
methods.sort_module_list(env)
+ if env.editor_build:
+ # Add editor-specific dependencies to the dependency graph.
+ env.module_add_dependencies("editor", ["freetype", "svg"])
+
+ # And check if they are met.
+ if not env.module_check_dependencies("editor"):
+ print("Not all modules required by editor builds are enabled.")
+ Exit(255)
+
methods.generate_version_header(env.module_version_string)
env["PROGSUFFIX_WRAP"] = suffix + env.module_version_string + ".console" + env["PROGSUFFIX"]
@@ -850,7 +860,7 @@ if selected_platform in platform_list:
if env["disable_3d"]:
if env.editor_build:
- print("Build option 'disable_3d=yes' cannot be used for editor builds, but only for export templates.")
+ print("Build option 'disable_3d=yes' cannot be used for editor builds, only for export template builds.")
Exit(255)
else:
env.Append(CPPDEFINES=["_3D_DISABLED"])
@@ -858,13 +868,15 @@ if selected_platform in platform_list:
if env.editor_build:
print(
"Build option 'disable_advanced_gui=yes' cannot be used for editor builds, "
- "but only for export templates."
+ "only for export template builds."
)
Exit(255)
else:
env.Append(CPPDEFINES=["ADVANCED_GUI_DISABLED"])
if env["minizip"]:
env.Append(CPPDEFINES=["MINIZIP_ENABLED"])
+ if env["brotli"]:
+ env.Append(CPPDEFINES=["BROTLI_ENABLED"])
if not env["verbose"]:
methods.no_verbose(sys, env)
diff --git a/core/SCsub b/core/SCsub
index 43deff3ad5..a0176f6c33 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -64,6 +64,31 @@ thirdparty_misc_sources = [
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
+# Brotli
+if env["brotli"]:
+ thirdparty_brotli_dir = "#thirdparty/brotli/"
+ thirdparty_brotli_sources = [
+ "common/constants.c",
+ "common/context.c",
+ "common/dictionary.c",
+ "common/platform.c",
+ "common/shared_dictionary.c",
+ "common/transform.c",
+ "dec/bit_reader.c",
+ "dec/decode.c",
+ "dec/huffman.c",
+ "dec/state.c",
+ ]
+ thirdparty_brotli_sources = [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
+
+ env_thirdparty.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
+ env.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
+
+ if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
+ env_thirdparty.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
+
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
+
# Zlib library, can be unbundled
if env["builtin_zlib"]:
thirdparty_zlib_dir = "#thirdparty/zlib/"
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index f82600a9a2..81ee09f515 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -66,7 +66,9 @@ int RemoteDebuggerPeerTCP::get_max_message_size() const {
void RemoteDebuggerPeerTCP::close() {
running = false;
- thread.wait_to_finish();
+ if (thread.is_started()) {
+ thread.wait_to_finish();
+ }
tcp_client->disconnect_from_host();
out_buf.clear();
in_buf.clear();
diff --git a/core/doc_data.h b/core/doc_data.h
index d064818cd5..4e0db89984 100644
--- a/core/doc_data.h
+++ b/core/doc_data.h
@@ -315,61 +315,6 @@ public:
}
};
- struct EnumDoc {
- String name = "@unnamed_enum";
- bool is_bitfield = false;
- String description;
- Vector<DocData::ConstantDoc> values;
- static EnumDoc from_dict(const Dictionary &p_dict) {
- EnumDoc doc;
-
- if (p_dict.has("name")) {
- doc.name = p_dict["name"];
- }
-
- if (p_dict.has("is_bitfield")) {
- doc.is_bitfield = p_dict["is_bitfield"];
- }
-
- if (p_dict.has("description")) {
- doc.description = p_dict["description"];
- }
-
- Array values;
- if (p_dict.has("values")) {
- values = p_dict["values"];
- }
- for (int i = 0; i < values.size(); i++) {
- doc.values.push_back(ConstantDoc::from_dict(values[i]));
- }
-
- return doc;
- }
- static Dictionary to_dict(const EnumDoc &p_doc) {
- Dictionary dict;
-
- if (!p_doc.name.is_empty()) {
- dict["name"] = p_doc.name;
- }
-
- dict["is_bitfield"] = p_doc.is_bitfield;
-
- if (!p_doc.description.is_empty()) {
- dict["description"] = p_doc.description;
- }
-
- if (!p_doc.values.is_empty()) {
- Array values;
- for (int i = 0; i < p_doc.values.size(); i++) {
- values.push_back(ConstantDoc::to_dict(p_doc.values[i]));
- }
- dict["values"] = values;
- }
-
- return dict;
- }
- };
-
struct PropertyDoc {
String name;
String type;
diff --git a/core/io/compression.cpp b/core/io/compression.cpp
index a6114e4f63..ac4a637597 100644
--- a/core/io/compression.cpp
+++ b/core/io/compression.cpp
@@ -35,11 +35,18 @@
#include "thirdparty/misc/fastlz.h"
+#ifdef BROTLI_ENABLED
+#include "thirdparty/brotli/include/brotli/decode.h"
+#endif
+
#include <zlib.h>
#include <zstd.h>
int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+ ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
+ } break;
case MODE_FASTLZ: {
if (p_src_size < 16) {
uint8_t src[16];
@@ -95,6 +102,9 @@ int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size,
int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+ ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
+ } break;
case MODE_FASTLZ: {
int ss = p_src_size + p_src_size * 6 / 100;
if (ss < 66) {
@@ -129,6 +139,16 @@ int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+#ifdef BROTLI_ENABLED
+ size_t ret_size = p_dst_max_size;
+ BrotliDecoderResult res = BrotliDecoderDecompress(p_src_size, p_src, &ret_size, p_dst);
+ ERR_FAIL_COND_V(res != BROTLI_DECODER_RESULT_SUCCESS, -1);
+ return ret_size;
+#else
+ ERR_FAIL_V_MSG(-1, "Godot was compiled without brotli support.");
+#endif
+ } break;
case MODE_FASTLZ: {
int ret_size = 0;
@@ -186,87 +206,147 @@ int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
*/
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
- int ret;
uint8_t *dst = nullptr;
int out_mark = 0;
- z_stream strm;
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
- // This function only supports GZip and Deflate
- int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
- ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
-
- // Initialize the stream
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = 0;
- strm.next_in = Z_NULL;
-
- int err = inflateInit2(&strm, window_bits);
- ERR_FAIL_COND_V(err != Z_OK, -1);
-
- // Setup the stream inputs
- strm.next_in = (Bytef *)p_src;
- strm.avail_in = p_src_size;
-
- // Ensure the destination buffer is empty
- p_dst_vect->clear();
-
- // decompress until deflate stream ends or end of file
- do {
- // Add another chunk size to the output buffer
- // This forces a copy of the whole buffer
- p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
- // Get pointer to the actual output buffer
- dst = p_dst_vect->ptrw();
-
- // Set the stream to the new output stream
- // Since it was copied, we need to reset the stream to the new buffer
- strm.next_out = &(dst[out_mark]);
- strm.avail_out = gzip_chunk;
-
- // run inflate() on input until output buffer is full and needs to be resized
- // or input runs out
+ if (p_mode == MODE_BROTLI) {
+#ifdef BROTLI_ENABLED
+ BrotliDecoderResult ret;
+ BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+ ERR_FAIL_COND_V(state == nullptr, Z_DATA_ERROR);
+
+ // Setup the stream inputs.
+ const uint8_t *next_in = p_src;
+ size_t avail_in = p_src_size;
+ uint8_t *next_out = nullptr;
+ size_t avail_out = 0;
+ size_t total_out = 0;
+
+ // Ensure the destination buffer is empty.
+ p_dst_vect->clear();
+
+ // Decompress until stream ends or end of file.
do {
- ret = inflate(&strm, Z_SYNC_FLUSH);
-
- switch (ret) {
- case Z_NEED_DICT:
- ret = Z_DATA_ERROR;
- [[fallthrough]];
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- case Z_STREAM_ERROR:
- case Z_BUF_ERROR:
- if (strm.msg) {
- WARN_PRINT(strm.msg);
- }
- (void)inflateEnd(&strm);
- p_dst_vect->clear();
- return ret;
+ // Add another chunk size to the output buffer.
+ // This forces a copy of the whole buffer.
+ p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
+ // Get pointer to the actual output buffer.
+ dst = p_dst_vect->ptrw();
+
+ // Set the stream to the new output stream.
+ // Since it was copied, we need to reset the stream to the new buffer.
+ next_out = &(dst[out_mark]);
+ avail_out += gzip_chunk;
+
+ ret = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, &total_out);
+ if (ret == BROTLI_DECODER_RESULT_ERROR) {
+ WARN_PRINT(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)));
+ BrotliDecoderDestroyInstance(state);
+ p_dst_vect->clear();
+ return Z_DATA_ERROR;
}
- } while (strm.avail_out > 0 && strm.avail_in > 0);
- out_mark += gzip_chunk;
+ out_mark += gzip_chunk - avail_out;
- // Enforce max output size
- if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
- (void)inflateEnd(&strm);
- p_dst_vect->clear();
- return Z_BUF_ERROR;
+ // Enforce max output size.
+ if (p_max_dst_size > -1 && total_out > (uint64_t)p_max_dst_size) {
+ BrotliDecoderDestroyInstance(state);
+ p_dst_vect->clear();
+ return Z_BUF_ERROR;
+ }
+ } while (ret != BROTLI_DECODER_RESULT_SUCCESS);
+
+ // If all done successfully, resize the output if it's larger than the actual output.
+ if ((unsigned long)p_dst_vect->size() > total_out) {
+ p_dst_vect->resize(total_out);
}
- } while (ret != Z_STREAM_END);
- // If all done successfully, resize the output if it's larger than the actual output
- if ((unsigned long)p_dst_vect->size() > strm.total_out) {
- p_dst_vect->resize(strm.total_out);
- }
+ // Clean up and return.
+ BrotliDecoderDestroyInstance(state);
+ return Z_OK;
+#else
+ ERR_FAIL_V_MSG(Z_ERRNO, "Godot was compiled without brotli support.");
+#endif
+ } else {
+ // This function only supports GZip and Deflate.
+ ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
+
+ int ret;
+ z_stream strm;
+ int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
+
+ // Initialize the stream.
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+
+ int err = inflateInit2(&strm, window_bits);
+ ERR_FAIL_COND_V(err != Z_OK, -1);
+
+ // Setup the stream inputs.
+ strm.next_in = (Bytef *)p_src;
+ strm.avail_in = p_src_size;
+
+ // Ensure the destination buffer is empty.
+ p_dst_vect->clear();
+
+ // Decompress until deflate stream ends or end of file.
+ do {
+ // Add another chunk size to the output buffer.
+ // This forces a copy of the whole buffer.
+ p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
+ // Get pointer to the actual output buffer.
+ dst = p_dst_vect->ptrw();
+
+ // Set the stream to the new output stream.
+ // Since it was copied, we need to reset the stream to the new buffer.
+ strm.next_out = &(dst[out_mark]);
+ strm.avail_out = gzip_chunk;
+
+ // Run inflate() on input until output buffer is full and needs to be resized or input runs out.
+ do {
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+
+ switch (ret) {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ [[fallthrough]];
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ case Z_STREAM_ERROR:
+ case Z_BUF_ERROR:
+ if (strm.msg) {
+ WARN_PRINT(strm.msg);
+ }
+ (void)inflateEnd(&strm);
+ p_dst_vect->clear();
+ return ret;
+ }
+ } while (strm.avail_out > 0 && strm.avail_in > 0);
+
+ out_mark += gzip_chunk;
+
+ // Enforce max output size.
+ if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
+ (void)inflateEnd(&strm);
+ p_dst_vect->clear();
+ return Z_BUF_ERROR;
+ }
+ } while (ret != Z_STREAM_END);
+
+ // If all done successfully, resize the output if it's larger than the actual output.
+ if ((unsigned long)p_dst_vect->size() > strm.total_out) {
+ p_dst_vect->resize(strm.total_out);
+ }
- // clean up and return
- (void)inflateEnd(&strm);
- return Z_OK;
+ // Clean up and return.
+ (void)inflateEnd(&strm);
+ return Z_OK;
+ }
}
int Compression::zlib_level = Z_DEFAULT_COMPRESSION;
diff --git a/core/io/compression.h b/core/io/compression.h
index 063da6dc7d..a5a2d657da 100644
--- a/core/io/compression.h
+++ b/core/io/compression.h
@@ -47,7 +47,8 @@ public:
MODE_FASTLZ,
MODE_DEFLATE,
MODE_ZSTD,
- MODE_GZIP
+ MODE_GZIP,
+ MODE_BROTLI
};
static int compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD);
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index 3d10151327..a6a1a224b3 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -871,4 +871,5 @@ void FileAccess::_bind_methods() {
BIND_ENUM_CONSTANT(COMPRESSION_DEFLATE);
BIND_ENUM_CONSTANT(COMPRESSION_ZSTD);
BIND_ENUM_CONSTANT(COMPRESSION_GZIP);
+ BIND_ENUM_CONSTANT(COMPRESSION_BROTLI);
}
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 47770cad87..34c80b3dd9 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -64,7 +64,8 @@ public:
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
- COMPRESSION_GZIP = Compression::MODE_GZIP
+ COMPRESSION_GZIP = Compression::MODE_GZIP,
+ COMPRESSION_BROTLI = Compression::MODE_BROTLI,
};
typedef void (*FileCloseFailNotify)(const String &);
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 139dc3afb1..63f7c80bdd 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -32,6 +32,8 @@
#include "core/variant/typed_array.h"
+#define GET_POINT_UNCHECKED(m_id) points[m_id.y - region.position.y][m_id.x - region.position.x]
+
static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) {
real_t dx = (real_t)ABS(p_to.x - p_from.x);
real_t dy = (real_t)ABS(p_to.y - p_from.y);
@@ -59,16 +61,29 @@ static real_t heuristic_chebyshev(const Vector2i &p_from, const Vector2i &p_to)
static real_t (*heuristics[AStarGrid2D::HEURISTIC_MAX])(const Vector2i &, const Vector2i &) = { heuristic_euclidian, heuristic_manhattan, heuristic_octile, heuristic_chebyshev };
+void AStarGrid2D::set_region(const Rect2i &p_region) {
+ ERR_FAIL_COND(p_region.size.x < 0 || p_region.size.y < 0);
+ if (p_region != region) {
+ region = p_region;
+ dirty = true;
+ }
+}
+
+Rect2i AStarGrid2D::get_region() const {
+ return region;
+}
+
void AStarGrid2D::set_size(const Size2i &p_size) {
+ WARN_DEPRECATED_MSG(R"(The "size" property is deprecated, use "region" instead.)");
ERR_FAIL_COND(p_size.x < 0 || p_size.y < 0);
- if (p_size != size) {
- size = p_size;
+ if (p_size != region.size) {
+ region.size = p_size;
dirty = true;
}
}
Size2i AStarGrid2D::get_size() const {
- return size;
+ return region.size;
}
void AStarGrid2D::set_offset(const Vector2 &p_offset) {
@@ -95,9 +110,11 @@ Size2 AStarGrid2D::get_cell_size() const {
void AStarGrid2D::update() {
points.clear();
- for (int64_t y = 0; y < size.y; y++) {
+ const int64_t end_x = region.position.x + region.size.width;
+ const int64_t end_y = region.position.y + region.size.height;
+ for (int64_t y = region.position.y; y < end_y; y++) {
LocalVector<Point> line;
- for (int64_t x = 0; x < size.x; x++) {
+ for (int64_t x = region.position.x; x < end_x; x++) {
line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size));
}
points.push_back(line);
@@ -106,11 +123,11 @@ void AStarGrid2D::update() {
}
bool AStarGrid2D::is_in_bounds(int p_x, int p_y) const {
- return p_x >= 0 && p_x < size.width && p_y >= 0 && p_y < size.height;
+ return region.has_point(Vector2i(p_x, p_y));
}
bool AStarGrid2D::is_in_boundsv(const Vector2i &p_id) const {
- return p_id.x >= 0 && p_id.x < size.width && p_id.y >= 0 && p_id.y < size.height;
+ return region.has_point(p_id);
}
bool AStarGrid2D::is_dirty() const {
@@ -154,27 +171,27 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const {
void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- points[p_id.y][p_id.x].solid = p_solid;
+ ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region));
+ GET_POINT_UNCHECKED(p_id).solid = p_solid;
}
bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].solid;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).solid;
}
void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set point's weight scale. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
+ ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set point's weight scale. Point %s out of bounds %s.", p_id, region));
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
- points[p_id.y][p_id.x].weight_scale = p_weight_scale;
+ GET_POINT_UNCHECKED(p_id).weight_scale = p_weight_scale;
}
real_t AStarGrid2D::get_point_weight_scale(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, 0, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), 0, vformat("Can't get point's weight scale. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].weight_scale;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), 0, vformat("Can't get point's weight scale. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).weight_scale;
}
AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) {
@@ -285,15 +302,15 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
bool has_left = false;
bool has_right = false;
- if (p_point->id.x - 1 >= 0) {
+ if (p_point->id.x - 1 >= region.position.x) {
left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y);
has_left = true;
}
- if (p_point->id.x + 1 < size.width) {
+ if (p_point->id.x + 1 < region.position.x + region.size.width) {
right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y);
has_right = true;
}
- if (p_point->id.y - 1 >= 0) {
+ if (p_point->id.y - 1 >= region.position.y) {
top = _get_point_unchecked(p_point->id.x, p_point->id.y - 1);
if (has_left) {
top_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y - 1);
@@ -302,7 +319,7 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
top_right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y - 1);
}
}
- if (p_point->id.y + 1 < size.height) {
+ if (p_point->id.y + 1 < region.position.y + region.size.height) {
bottom = _get_point_unchecked(p_point->id.x, p_point->id.y + 1);
if (has_left) {
bottom_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y + 1);
@@ -461,19 +478,19 @@ real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_t
void AStarGrid2D::clear() {
points.clear();
- size = Vector2i();
+ region = Rect2i();
}
Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, Vector2(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), Vector2(), vformat("Can't get point's position. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].pos;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), Vector2(), vformat("Can't get point's position. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).pos;
}
Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_to_id, region));
Point *a = _get_point(p_from_id.x, p_from_id.y);
Point *b = _get_point(p_to_id.x, p_to_id.y);
@@ -520,8 +537,8 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec
TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
ERR_FAIL_COND_V_MSG(dirty, TypedArray<Vector2i>(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point %s out of bounds %s.", p_to_id, region));
Point *a = _get_point(p_from_id.x, p_from_id.y);
Point *b = _get_point(p_to_id.x, p_to_id.y);
@@ -565,6 +582,8 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V
}
void AStarGrid2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_region", "region"), &AStarGrid2D::set_region);
+ ClassDB::bind_method(D_METHOD("get_region"), &AStarGrid2D::get_region);
ClassDB::bind_method(D_METHOD("set_size", "size"), &AStarGrid2D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &AStarGrid2D::get_size);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &AStarGrid2D::set_offset);
@@ -596,6 +615,7 @@ void AStarGrid2D::_bind_methods() {
GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
+ ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "region"), "set_region", "get_region");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size"), "set_cell_size", "get_cell_size");
@@ -617,3 +637,5 @@ void AStarGrid2D::_bind_methods() {
BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES);
BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX);
}
+
+#undef GET_POINT_UNCHECKED
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index e4e62ec360..50df58e0e9 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -58,7 +58,7 @@ public:
};
private:
- Size2i size;
+ Rect2i region;
Vector2 offset;
Size2 cell_size = Size2(1, 1);
bool dirty = false;
@@ -107,21 +107,21 @@ private:
private: // Internal routines.
_FORCE_INLINE_ bool _is_walkable(int64_t p_x, int64_t p_y) const {
- if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) {
- return !points[p_y][p_x].solid;
+ if (region.has_point(Vector2i(p_x, p_y))) {
+ return !points[p_y - region.position.y][p_x - region.position.x].solid;
}
return false;
}
_FORCE_INLINE_ Point *_get_point(int64_t p_x, int64_t p_y) {
- if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) {
- return &points[p_y][p_x];
+ if (region.has_point(Vector2i(p_x, p_y))) {
+ return &points[p_y - region.position.y][p_x - region.position.x];
}
return nullptr;
}
_FORCE_INLINE_ Point *_get_point_unchecked(int64_t p_x, int64_t p_y) {
- return &points[p_y][p_x];
+ return &points[p_y - region.position.y][p_x - region.position.x];
}
void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors);
@@ -138,6 +138,9 @@ protected:
GDVIRTUAL2RC(real_t, _compute_cost, Vector2i, Vector2i)
public:
+ void set_region(const Rect2i &p_region);
+ Rect2i get_region() const;
+
void set_size(const Size2i &p_size);
Size2i get_size() const;
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index a602dc9fb8..760f3bfd0c 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -53,8 +53,10 @@ MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint3
#endif
ClassDB::APIType ClassDB::current_api = API_CORE;
+HashMap<ClassDB::APIType, uint64_t> ClassDB::api_hashes_cache;
void ClassDB::set_current_api(APIType p_api) {
+ DEV_ASSERT(!api_hashes_cache.has(p_api)); // This API type may not be suitable for caching of hash if it can change later.
current_api = p_api;
}
@@ -165,6 +167,10 @@ uint64_t ClassDB::get_api_hash(APIType p_api) {
OBJTYPE_RLOCK;
#ifdef DEBUG_METHODS_ENABLED
+ if (api_hashes_cache.has(p_api)) {
+ return api_hashes_cache[p_api];
+ }
+
uint64_t hash = hash_murmur3_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG));
List<StringName> class_list;
@@ -290,7 +296,14 @@ uint64_t ClassDB::get_api_hash(APIType p_api) {
}
}
- return hash_fmix32(hash);
+ hash = hash_fmix32(hash);
+
+ // Extension API changes at runtime; let's just not cache them by now.
+ if (p_api != API_EXTENSION && p_api != API_EDITOR_EXTENSION) {
+ api_hashes_cache[p_api] = hash;
+ }
+
+ return hash;
#else
return 0;
#endif
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 0b62cf40f7..1a5e9235cf 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -154,6 +154,7 @@ public:
#endif
static APIType current_api;
+ static HashMap<APIType, uint64_t> api_hashes_cache;
static void _add_class2(const StringName &p_class, const StringName &p_inherits);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 2e1ea9ef5f..d96f4eb9fc 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -38,6 +38,7 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
+#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
#ifdef DEBUG_ENABLED
@@ -1019,20 +1020,23 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
List<_ObjectSignalDisconnectData> disconnect_data;
- //copy on write will ensure that disconnecting the signal or even deleting the object will not affect the signal calling.
- //this happens automatically and will not change the performance of calling.
- //awesome, isn't it?
- VMap<Callable, SignalData::Slot> slot_map = s->slot_map;
-
- int ssize = slot_map.size();
+ // Ensure that disconnecting the signal or even deleting the object
+ // will not affect the signal calling.
+ LocalVector<Connection> slot_conns;
+ slot_conns.resize(s->slot_map.size());
+ {
+ uint32_t idx = 0;
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ slot_conns[idx++] = slot_kv.value.conn;
+ }
+ DEV_ASSERT(idx == s->slot_map.size());
+ }
OBJ_DEBUG_LOCK
Error err = OK;
- for (int i = 0; i < ssize; i++) {
- const Connection &c = slot_map.getv(i).conn;
-
+ for (const Connection &c : slot_conns) {
Object *target = c.callable.get_object();
if (!target) {
// Target might have been deleted during signal callback, this is expected and OK.
@@ -1195,8 +1199,8 @@ void Object::get_all_signal_connections(List<Connection> *p_connections) const {
for (const KeyValue<StringName, SignalData> &E : signal_map) {
const SignalData *s = &E.value;
- for (int i = 0; i < s->slot_map.size(); i++) {
- p_connections->push_back(s->slot_map.getv(i).conn);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ p_connections->push_back(slot_kv.value.conn);
}
}
}
@@ -1207,8 +1211,8 @@ void Object::get_signal_connection_list(const StringName &p_signal, List<Connect
return; //nothing
}
- for (int i = 0; i < s->slot_map.size(); i++) {
- p_connections->push_back(s->slot_map.getv(i).conn);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ p_connections->push_back(slot_kv.value.conn);
}
}
@@ -1218,8 +1222,8 @@ int Object::get_persistent_signal_connection_count() const {
for (const KeyValue<StringName, SignalData> &E : signal_map) {
const SignalData *s = &E.value;
- for (int i = 0; i < s->slot_map.size(); i++) {
- if (s->slot_map.getv(i).conn.flags & CONNECT_PERSIST) {
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ if (slot_kv.value.conn.flags & CONNECT_PERSIST) {
count += 1;
}
}
@@ -1804,11 +1808,8 @@ Object::~Object() {
SignalData *s = &E.value;
//brute force disconnect for performance
- int slot_count = s->slot_map.size();
- const VMap<Callable, SignalData::Slot>::Pair *slot_list = s->slot_map.get_array();
-
- for (int i = 0; i < slot_count; i++) {
- slot_list[i].value.conn.callable.get_object()->connections.erase(slot_list[i].value.cE);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ slot_kv.value.conn.callable.get_object()->connections.erase(slot_kv.value.cE);
}
signal_map.erase(E.key);
diff --git a/core/object/object.h b/core/object/object.h
index 4226b5e67b..f0acfffe14 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -41,7 +41,6 @@
#include "core/templates/list.h"
#include "core/templates/rb_map.h"
#include "core/templates/safe_refcount.h"
-#include "core/templates/vmap.h"
#include "core/variant/callable_bind.h"
#include "core/variant/variant.h"
@@ -587,7 +586,7 @@ private:
};
MethodInfo user;
- VMap<Callable, Slot> slot_map;
+ HashMap<Callable, Slot, HashableHasher<Callable>> slot_map;
};
HashMap<StringName, SignalData> signal_map;
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index 92865576f3..502f82aaef 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -38,13 +38,9 @@
Thread::PlatformFunctions Thread::platform_functions;
-uint64_t Thread::_thread_id_hash(const std::thread::id &p_t) {
- static std::hash<std::thread::id> hasher;
- return hasher(p_t);
-}
+SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1.
-Thread::ID Thread::main_thread_id = _thread_id_hash(std::this_thread::get_id());
-thread_local Thread::ID Thread::caller_id = 0;
+thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID;
void Thread::_set_platform_functions(const PlatformFunctions &p_functions) {
platform_functions = p_functions;
@@ -71,31 +67,23 @@ void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_cal
}
void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) {
- if (id != _thread_id_hash(std::thread::id())) {
-#ifdef DEBUG_ENABLED
- WARN_PRINT("A Thread object has been re-started without wait_to_finish() having been called on it. Please do so to ensure correct cleanup of the thread.");
-#endif
- thread.detach();
- std::thread empty_thread;
- thread.swap(empty_thread);
- }
- std::thread new_thread(&Thread::callback, _thread_id_hash(thread.get_id()), p_settings, p_callback, p_user);
+ ERR_FAIL_COND_MSG(id != UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it.");
+ id = id_counter.increment();
+ std::thread new_thread(&Thread::callback, id, p_settings, p_callback, p_user);
thread.swap(new_thread);
- id = _thread_id_hash(thread.get_id());
}
bool Thread::is_started() const {
- return id != _thread_id_hash(std::thread::id());
+ return id != UNASSIGNED_ID;
}
void Thread::wait_to_finish() {
- if (id != _thread_id_hash(std::thread::id())) {
- ERR_FAIL_COND_MSG(id == get_caller_id(), "A Thread can't wait for itself to finish.");
- thread.join();
- std::thread empty_thread;
- thread.swap(empty_thread);
- id = _thread_id_hash(std::thread::id());
- }
+ ERR_FAIL_COND_MSG(id == UNASSIGNED_ID, "Attempt of waiting to finish on a thread that was never started.");
+ ERR_FAIL_COND_MSG(id == get_caller_id(), "Threads can't wait to finish on themselves, another thread must wait.");
+ thread.join();
+ std::thread empty_thread;
+ thread.swap(empty_thread);
+ id = UNASSIGNED_ID;
}
Error Thread::set_name(const String &p_name) {
@@ -107,11 +95,10 @@ Error Thread::set_name(const String &p_name) {
}
Thread::Thread() {
- caller_id = _thread_id_hash(std::this_thread::get_id());
}
Thread::~Thread() {
- if (id != _thread_id_hash(std::thread::id())) {
+ if (id != UNASSIGNED_ID) {
#ifdef DEBUG_ENABLED
WARN_PRINT("A Thread object has been destroyed without wait_to_finish() having been called on it. Please do so to ensure correct cleanup of the thread.");
#endif
diff --git a/core/os/thread.h b/core/os/thread.h
index 6eb21fba65..19e1376ca8 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -52,6 +52,11 @@ public:
typedef uint64_t ID;
+ enum : ID {
+ UNASSIGNED_ID = 0,
+ MAIN_ID = 1
+ };
+
enum Priority {
PRIORITY_LOW,
PRIORITY_NORMAL,
@@ -74,11 +79,8 @@ public:
private:
friend class Main;
- static ID main_thread_id;
-
- static uint64_t _thread_id_hash(const std::thread::id &p_t);
-
- ID id = _thread_id_hash(std::thread::id());
+ ID id = UNASSIGNED_ID;
+ static SafeNumeric<uint64_t> id_counter;
static thread_local ID caller_id;
std::thread thread;
@@ -86,14 +88,22 @@ private:
static PlatformFunctions platform_functions;
+ static void make_main_thread() { caller_id = MAIN_ID; }
+ static void release_main_thread() { caller_id = UNASSIGNED_ID; }
+
public:
static void _set_platform_functions(const PlatformFunctions &p_functions);
_FORCE_INLINE_ ID get_id() const { return id; }
// get the ID of the caller thread
- _FORCE_INLINE_ static ID get_caller_id() { return caller_id; }
+ _FORCE_INLINE_ static ID get_caller_id() {
+ if (unlikely(caller_id == UNASSIGNED_ID)) {
+ caller_id = id_counter.increment();
+ }
+ return caller_id;
+ }
// get the ID of the main thread
- _FORCE_INLINE_ static ID get_main_id() { return main_thread_id; }
+ _FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
static Error set_name(const String &p_name);
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index 95e6bad2f2..2a212f3dcb 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -386,6 +386,12 @@ struct HashMapHasherDefault {
}
};
+// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed
+template <class T>
+struct HashableHasher {
+ static _FORCE_INLINE_ uint32_t hash(const T &hashable) { return hashable.hash(); }
+};
+
template <typename T>
struct HashMapComparatorDefault {
static bool compare(const T &p_lhs, const T &p_rhs) {
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index e349f082d3..2a38d34cfb 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -5,11 +5,11 @@
</brief_description>
<description>
Compared to [AStar2D] you don't need to manually create points or connect them together. It also supports multiple type of heuristics and modes for diagonal movement. This class also provides a jumping mode which is faster to calculate than without it in the [AStar2D] class.
- In contrast to [AStar2D], you only need set the [member size] of the grid, optionally set the [member cell_size] and then call the [method update] method:
+ In contrast to [AStar2D], you only need set the [member region] of the grid, optionally set the [member cell_size] and then call the [method update] method:
[codeblocks]
[gdscript]
var astar_grid = AStarGrid2D.new()
- astar_grid.size = Vector2i(32, 32)
+ astar_grid.region = Rect2i(0, 0, 32, 32)
astar_grid.cell_size = Vector2(16, 16)
astar_grid.update()
print(astar_grid.get_id_path(Vector2i(0, 0), Vector2i(3, 4))) # prints (0, 0), (1, 1), (2, 2), (3, 3), (3, 4)
@@ -49,7 +49,7 @@
<method name="clear">
<return type="void" />
<description>
- Clears the grid and sets the [member size] to [constant Vector2i.ZERO].
+ Clears the grid and sets the [member region] to [code]Rect2i(0, 0, 0, 0)[/code].
</description>
</method>
<method name="get_id_path">
@@ -132,7 +132,8 @@
<method name="update">
<return type="void" />
<description>
- Updates the internal state of the grid according to the parameters to prepare it to search the path. Needs to be called if parameters like [member size], [member cell_size] or [member offset] are changed. [method is_dirty] will return [code]true[/code] if this is the case and this needs to be called.
+ Updates the internal state of the grid according to the parameters to prepare it to search the path. Needs to be called if parameters like [member region], [member cell_size] or [member offset] are changed. [method is_dirty] will return [code]true[/code] if this is the case and this needs to be called.
+ [b]Note:[/b] All point data (solidity and weight scale) will be cleared.
</description>
</method>
</methods>
@@ -156,8 +157,12 @@
<member name="offset" type="Vector2" setter="set_offset" getter="get_offset" default="Vector2(0, 0)">
The offset of the grid which will be applied to calculate the resulting point position returned by [method get_point_path]. If changed, [method update] needs to be called before finding the next path.
</member>
+ <member name="region" type="Rect2i" setter="set_region" getter="get_region" default="Rect2i(0, 0, 0, 0)">
+ The region of grid cells available for pathfinding. If changed, [method update] needs to be called before finding the next path.
+ </member>
<member name="size" type="Vector2i" setter="set_size" getter="get_size" default="Vector2i(0, 0)">
The size of the grid (number of cells of size [member cell_size] on each axis). If changed, [method update] needs to be called before finding the next path.
+ [b]Note:[/b] This property is deprecated, use [member region] instead.
</member>
</members>
<constants>
diff --git a/doc/classes/AnimationTrackEditPlugin.xml b/doc/classes/AnimationTrackEditPlugin.xml
deleted file mode 100644
index 34f18b2989..0000000000
--- a/doc/classes/AnimationTrackEditPlugin.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="AnimationTrackEditPlugin" inherits="RefCounted" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
- <brief_description>
- </brief_description>
- <description>
- </description>
- <tutorials>
- </tutorials>
-</class>
diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml
index 14656c649e..61a0606f79 100644
--- a/doc/classes/ColorPicker.xml
+++ b/doc/classes/ColorPicker.xml
@@ -141,6 +141,9 @@
</constant>
</constants>
<theme_items>
+ <theme_item name="center_slider_grabbers" data_type="constant" type="int" default="1">
+ Overrides the [theme_item HSlider.center_grabber] theme property of the sliders.
+ </theme_item>
<theme_item name="h_width" data_type="constant" type="int" default="30">
The width of the hue selection slider.
</theme_item>
diff --git a/doc/classes/Curve.xml b/doc/classes/Curve.xml
index 317a578559..52f79ecc9d 100644
--- a/doc/classes/Curve.xml
+++ b/doc/classes/Curve.xml
@@ -78,7 +78,7 @@
<return type="void" />
<param index="0" name="index" type="int" />
<description>
- Removes the point at [code]index[/code] from the curve.
+ Removes the point at [param index] from the curve.
</description>
</method>
<method name="sample" qualifiers="const">
diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml
index d545c144ab..abf71c5754 100644
--- a/doc/classes/Curve2D.xml
+++ b/doc/classes/Curve2D.xml
@@ -80,7 +80,7 @@
<return type="void" />
<param index="0" name="idx" type="int" />
<description>
- Deletes the point [code]idx[/code] from the curve. Sends an error to the console if [code]idx[/code] is out of bounds.
+ Deletes the point [param idx] from the curve. Sends an error to the console if [param idx] is out of bounds.
</description>
</method>
<method name="sample" qualifiers="const">
diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml
index 4134df25dd..4d1f5a7180 100644
--- a/doc/classes/Curve3D.xml
+++ b/doc/classes/Curve3D.xml
@@ -100,7 +100,7 @@
<return type="void" />
<param index="0" name="idx" type="int" />
<description>
- Deletes the point [code]idx[/code] from the curve. Sends an error to the console if [code]idx[/code] is out of bounds.
+ Deletes the point [param idx] from the curve. Sends an error to the console if [param idx] is out of bounds.
</description>
</method>
<method name="sample" qualifiers="const">
diff --git a/doc/classes/EditorScript.xml b/doc/classes/EditorScript.xml
index ecaa21efb3..d77d11857b 100644
--- a/doc/classes/EditorScript.xml
+++ b/doc/classes/EditorScript.xml
@@ -48,13 +48,13 @@
[b]Warning:[/b] The implementation of this method is currently disabled.
</description>
</method>
- <method name="get_editor_interface">
+ <method name="get_editor_interface" qualifiers="const">
<return type="EditorInterface" />
<description>
Returns the [EditorInterface] singleton instance.
</description>
</method>
- <method name="get_scene">
+ <method name="get_scene" qualifiers="const">
<return type="Node" />
<description>
Returns the Editor's currently active scene.
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 14ef879f95..ddaed23d55 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -571,6 +571,9 @@
If [code]true[/code], editor main menu is using embedded [MenuBar] instead of system global menu.
Specific to the macOS platform.
</member>
+ <member name="interface/inspector/float_drag_speed" type="float" setter="" getter="">
+ Base speed for increasing/decreasing float values by dragging them in the inspector.
+ </member>
<member name="interface/inspector/max_array_dictionary_items_per_page" type="int" setter="" getter="">
The number of [Array] or [Dictionary] items to display on each "page" in the inspector. Higher values allow viewing more values per page, but take more time to load. This increased load time is noticeable when selecting nodes that have array or dictionary properties in the editor.
</member>
diff --git a/doc/classes/EditorVCSInterface.xml b/doc/classes/EditorVCSInterface.xml
index d5dc6abe81..031d2a50c3 100644
--- a/doc/classes/EditorVCSInterface.xml
+++ b/doc/classes/EditorVCSInterface.xml
@@ -13,21 +13,21 @@
<return type="bool" />
<param index="0" name="branch_name" type="String" />
<description>
- Checks out a [code]branch_name[/code] in the VCS.
+ Checks out a [param branch_name] in the VCS.
</description>
</method>
<method name="_commit" qualifiers="virtual">
<return type="void" />
<param index="0" name="msg" type="String" />
<description>
- Commits the currently staged changes and applies the commit [code]msg[/code] to the resulting commit.
+ Commits the currently staged changes and applies the commit [param msg] to the resulting commit.
</description>
</method>
<method name="_create_branch" qualifiers="virtual">
<return type="void" />
<param index="0" name="branch_name" type="String" />
<description>
- Creates a new branch named [code]branch_name[/code] in the VCS.
+ Creates a new branch named [param branch_name] in the VCS.
</description>
</method>
<method name="_create_remote" qualifiers="virtual">
@@ -35,21 +35,21 @@
<param index="0" name="remote_name" type="String" />
<param index="1" name="remote_url" type="String" />
<description>
- Creates a new remote destination with name [code]remote_name[/code] and points it to [code]remote_url[/code]. This can be an HTTPS remote or an SSH remote.
+ Creates a new remote destination with name [param remote_name] and points it to [param remote_url]. This can be an HTTPS remote or an SSH remote.
</description>
</method>
<method name="_discard_file" qualifiers="virtual">
<return type="void" />
<param index="0" name="file_path" type="String" />
<description>
- Discards the changes made in a file present at [code]file_path[/code].
+ Discards the changes made in a file present at [param file_path].
</description>
</method>
<method name="_fetch" qualifiers="virtual">
<return type="void" />
<param index="0" name="remote" type="String" />
<description>
- Fetches new changes from the remote, but doesn't write changes to the current working directory. Equivalent to [code]git fetch[/code].
+ Fetches new changes from the [param remote], but doesn't write changes to the current working directory. Equivalent to [code]git fetch[/code].
</description>
</method>
<method name="_get_branch_list" qualifiers="virtual">
@@ -69,7 +69,7 @@
<param index="0" name="identifier" type="String" />
<param index="1" name="area" type="int" />
<description>
- Returns an array of [Dictionary] items (see [method create_diff_file], [method create_diff_hunk], [method create_diff_line], [method add_line_diffs_into_diff_hunk] and [method add_diff_hunks_into_diff_file]), each containing information about a diff. If [code]identifier[/code] is a file path, returns a file diff, and if it is a commit identifier, then returns a commit diff.
+ Returns an array of [Dictionary] items (see [method create_diff_file], [method create_diff_hunk], [method create_diff_line], [method add_line_diffs_into_diff_hunk] and [method add_diff_hunks_into_diff_file]), each containing information about a diff. If [param identifier] is a file path, returns a file diff, and if it is a commit identifier, then returns a commit diff.
</description>
</method>
<method name="_get_line_diff" qualifiers="virtual">
@@ -77,7 +77,7 @@
<param index="0" name="file_path" type="String" />
<param index="1" name="text" type="String" />
<description>
- Returns an [Array] of [Dictionary] items (see [method create_diff_hunk]), each containing a line diff between a file at [code]file_path[/code] and the [code]text[/code] which is passed in.
+ Returns an [Array] of [Dictionary] items (see [method create_diff_hunk]), each containing a line diff between a file at [param file_path] and the [param text] which is passed in.
</description>
</method>
<method name="_get_modified_files_data" qualifiers="virtual">
@@ -109,7 +109,7 @@
<return type="bool" />
<param index="0" name="project_path" type="String" />
<description>
- Initializes the VCS plugin when called from the editor. Returns whether or not the plugin was successfully initialized. A VCS project is initialized at [code]project_path[/code].
+ Initializes the VCS plugin when called from the editor. Returns whether or not the plugin was successfully initialized. A VCS project is initialized at [param project_path].
</description>
</method>
<method name="_pull" qualifiers="virtual">
@@ -149,7 +149,7 @@
<param index="3" name="ssh_private_key_path" type="String" />
<param index="4" name="ssh_passphrase" type="String" />
<description>
- Set user credentials in the underlying VCS. [code]username[/code] and [code]password[/code] are used only during HTTPS authentication unless not already mentioned in the remote URL. [code]ssh_public_key_path[/code], [code]ssh_private_key_path[/code], and [code]ssh_passphrase[/code] are only used during SSH authentication.
+ Set user credentials in the underlying VCS. [param username] and [param password] are used only during HTTPS authentication unless not already mentioned in the remote URL. [param ssh_public_key_path], [param ssh_private_key_path], and [param ssh_passphrase] are only used during SSH authentication.
</description>
</method>
<method name="_shut_down" qualifiers="virtual">
@@ -162,14 +162,14 @@
<return type="void" />
<param index="0" name="file_path" type="String" />
<description>
- Stages the file present at [code]file_path[/code] to the staged area.
+ Stages the file present at [param file_path] to the staged area.
</description>
</method>
<method name="_unstage_file" qualifiers="virtual">
<return type="void" />
<param index="0" name="file_path" type="String" />
<description>
- Unstages the file present at [code]file_path[/code] from the staged area to the unstaged area.
+ Unstages the file present at [param file_path] from the staged area to the unstaged area.
</description>
</method>
<method name="add_diff_hunks_into_diff_file">
@@ -177,7 +177,7 @@
<param index="0" name="diff_file" type="Dictionary" />
<param index="1" name="diff_hunks" type="Dictionary[]" />
<description>
- Helper function to add an array of [code]diff_hunks[/code] into a [code]diff_file[/code].
+ Helper function to add an array of [param diff_hunks] into a [param diff_file].
</description>
</method>
<method name="add_line_diffs_into_diff_hunk">
@@ -185,7 +185,7 @@
<param index="0" name="diff_hunk" type="Dictionary" />
<param index="1" name="line_diffs" type="Dictionary[]" />
<description>
- Helper function to add an array of [code]line_diffs[/code] into a [code]diff_hunk[/code].
+ Helper function to add an array of [param line_diffs] into a [param diff_hunk].
</description>
</method>
<method name="create_commit">
@@ -196,7 +196,7 @@
<param index="3" name="unix_timestamp" type="int" />
<param index="4" name="offset_minutes" type="int" />
<description>
- Helper function to create a commit [Dictionary] item. [code]msg[/code] is the commit message of the commit. [code]author[/code] is a single human-readable string containing all the author's details, e.g. the email and name configured in the VCS. [code]id[/code] is the identifier of the commit, in whichever format your VCS may provide an identifier to commits. [code]unix_timestamp[/code] is the UTC Unix timestamp of when the commit was created. [code]offset_minutes[/code] is the timezone offset in minutes, recorded from the system timezone where the commit was created.
+ Helper function to create a commit [Dictionary] item. [param msg] is the commit message of the commit. [param author] is a single human-readable string containing all the author's details, e.g. the email and name configured in the VCS. [param id] is the identifier of the commit, in whichever format your VCS may provide an identifier to commits. [param unix_timestamp] is the UTC Unix timestamp of when the commit was created. [param offset_minutes] is the timezone offset in minutes, recorded from the system timezone where the commit was created.
</description>
</method>
<method name="create_diff_file">
@@ -204,7 +204,7 @@
<param index="0" name="new_file" type="String" />
<param index="1" name="old_file" type="String" />
<description>
- Helper function to create a [code]Dictionary[/code] for storing old and new diff file paths.
+ Helper function to create a [Dictionary] for storing old and new diff file paths.
</description>
</method>
<method name="create_diff_hunk">
@@ -214,7 +214,7 @@
<param index="2" name="old_lines" type="int" />
<param index="3" name="new_lines" type="int" />
<description>
- Helper function to create a [code]Dictionary[/code] for storing diff hunk data. [code]old_start[/code] is the starting line number in old file. [code]new_start[/code] is the starting line number in new file. [code]old_lines[/code] is the number of lines in the old file. [code]new_lines[/code] is the number of lines in the new file.
+ Helper function to create a [Dictionary] for storing diff hunk data. [param old_start] is the starting line number in old file. [param new_start] is the starting line number in new file. [param old_lines] is the number of lines in the old file. [param new_lines] is the number of lines in the new file.
</description>
</method>
<method name="create_diff_line">
@@ -224,7 +224,7 @@
<param index="2" name="content" type="String" />
<param index="3" name="status" type="String" />
<description>
- Helper function to create a [code]Dictionary[/code] for storing a line diff. [code]new_line_no[/code] is the line number in the new file (can be [code]-1[/code] if the line is deleted). [code]old_line_no[/code] is the line number in the old file (can be [code]-1[/code] if the line is added). [code]content[/code] is the diff text. [code]status[/code] is a single character string which stores the line origin.
+ Helper function to create a [Dictionary] for storing a line diff. [param new_line_no] is the line number in the new file (can be [code]-1[/code] if the line is deleted). [param old_line_no] is the line number in the old file (can be [code]-1[/code] if the line is added). [param content] is the diff text. [param status] is a single character string which stores the line origin.
</description>
</method>
<method name="create_status_file">
@@ -233,7 +233,7 @@
<param index="1" name="change_type" type="int" enum="EditorVCSInterface.ChangeType" />
<param index="2" name="area" type="int" enum="EditorVCSInterface.TreeArea" />
<description>
- Helper function to create a [code]Dictionary[/code] used by editor to read the status of a file.
+ Helper function to create a [Dictionary] used by editor to read the status of a file.
</description>
</method>
<method name="popup_error">
diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml
index 9ea59010cc..aa15f88afa 100644
--- a/doc/classes/Engine.xml
+++ b/doc/classes/Engine.xml
@@ -306,7 +306,7 @@
[b]Note:[/b] This property does not impact the editor's Errors tab when running a project from the editor.
</member>
<member name="time_scale" type="float" setter="set_time_scale" getter="get_time_scale" default="1.0">
- Controls how fast or slow the in-game clock ticks versus the real life one. It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, whilst a value of 0.5 means the game moves at half the regular speed.
+ Controls how fast or slow the in-game clock ticks versus the real life one. It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, whilst a value of 0.5 means the game moves at half the regular speed. This also affects [Timer] and [SceneTreeTimer] (see [method SceneTree.create_timer] for how to control this).
</member>
</members>
</class>
diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml
index f8ca5326a0..45317be394 100644
--- a/doc/classes/EngineDebugger.xml
+++ b/doc/classes/EngineDebugger.xml
@@ -50,7 +50,7 @@
<param index="1" name="enable" type="bool" />
<param index="2" name="arguments" type="Array" default="[]" />
<description>
- Calls the [code]toggle[/code] callable of the profiler with given [param name] and [param arguments]. Enables/Disables the same profiler depending on [code]enable[/code] argument.
+ Calls the [code]toggle[/code] callable of the profiler with given [param name] and [param arguments]. Enables/Disables the same profiler depending on [param enable] argument.
</description>
</method>
<method name="register_message_capture">
diff --git a/doc/classes/Expression.xml b/doc/classes/Expression.xml
index 0796e837b2..ff04440a04 100644
--- a/doc/classes/Expression.xml
+++ b/doc/classes/Expression.xml
@@ -49,6 +49,7 @@
[/codeblocks]
</description>
<tutorials>
+ <link title="Evaluating Expressions">$DOCS_URL/tutorials/scripting/evaluating_expressions.html</link>
</tutorials>
<methods>
<method name="execute">
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index fb84943eb4..d1ee90142e 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -204,7 +204,7 @@
<return type="int" />
<param index="0" name="file" type="String" />
<description>
- Returns the last time the [param file] was modified in Unix timestamp format or returns a [String] "ERROR IN [code]file[/code]". This Unix timestamp can be converted to another format using the [Time] singleton.
+ Returns the last time the [param file] was modified in Unix timestamp format or returns a [String] "ERROR IN [param file]". This Unix timestamp can be converted to another format using the [Time] singleton.
</description>
</method>
<method name="get_open_error" qualifiers="static">
@@ -492,5 +492,8 @@
<constant name="COMPRESSION_GZIP" value="3" enum="CompressionMode">
Uses the [url=https://www.gzip.org/]gzip[/url] compression method.
</constant>
+ <constant name="COMPRESSION_BROTLI" value="4" enum="CompressionMode">
+ Uses the [url=https://github.com/google/brotli]brotli[/url] compression method (only decompression is supported).
+ </constant>
</constants>
</class>
diff --git a/doc/classes/Gradient.xml b/doc/classes/Gradient.xml
index 6026467b06..aa941f9fd3 100644
--- a/doc/classes/Gradient.xml
+++ b/doc/classes/Gradient.xml
@@ -49,13 +49,14 @@
<return type="void" />
<description>
Reverses/mirrors the gradient.
+ [b]Note:[/b] This method mirrors all points around the middle of the gradient, which may produce unexpected results when [member interpolation_mode] is set to [constant GRADIENT_INTERPOLATE_CONSTANT].
</description>
</method>
<method name="sample">
<return type="Color" />
<param index="0" name="offset" type="float" />
<description>
- Returns the interpolated color specified by [code]offset[/code].
+ Returns the interpolated color specified by [param offset].
</description>
</method>
<method name="set_color">
diff --git a/doc/classes/HSlider.xml b/doc/classes/HSlider.xml
index 5e11c1c3ab..2ab66a2e75 100644
--- a/doc/classes/HSlider.xml
+++ b/doc/classes/HSlider.xml
@@ -10,6 +10,9 @@
<tutorials>
</tutorials>
<theme_items>
+ <theme_item name="center_grabber" data_type="constant" type="int" default="0">
+ Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position.
+ </theme_item>
<theme_item name="grabber_offset" data_type="constant" type="int" default="0">
Vertical offset of the grabber.
</theme_item>
diff --git a/doc/classes/ImageTexture3D.xml b/doc/classes/ImageTexture3D.xml
index ebfd38c3f4..4396039ab2 100644
--- a/doc/classes/ImageTexture3D.xml
+++ b/doc/classes/ImageTexture3D.xml
@@ -26,7 +26,7 @@
<return type="void" />
<param index="0" name="data" type="Image[]" />
<description>
- Replaces the texture's existing data with the layers specified in [code]data[/code]. The size of [code]data[/code] must match the parameters that were used for [method create]. In other words, the texture cannot be resized or have its format changed by calling [method update].
+ Replaces the texture's existing data with the layers specified in [param data]. The size of [param data] must match the parameters that were used for [method create]. In other words, the texture cannot be resized or have its format changed by calling [method update].
</description>
</method>
</methods>
diff --git a/doc/classes/ImageTextureLayered.xml b/doc/classes/ImageTextureLayered.xml
index 7f17c8e897..215ff6ac5d 100644
--- a/doc/classes/ImageTextureLayered.xml
+++ b/doc/classes/ImageTextureLayered.xml
@@ -22,7 +22,7 @@
<param index="0" name="image" type="Image" />
<param index="1" name="layer" type="int" />
<description>
- Replaces the existing [Image] data at the given [code]layer[/code] with this new image.
+ Replaces the existing [Image] data at the given [param layer] with this new image.
The given [Image] must have the same width, height, image format and mipmapping setting (a [code]bool[/code] value) as the rest of the referenced images.
If the image format is unsupported, it will be decompressed and converted to a similar and supported [enum Image.Format].
The update is immediate: it's synchronized with drawing.
diff --git a/doc/classes/ImmediateMesh.xml b/doc/classes/ImmediateMesh.xml
index 504076e3fe..cd6cb32d3e 100644
--- a/doc/classes/ImmediateMesh.xml
+++ b/doc/classes/ImmediateMesh.xml
@@ -4,9 +4,20 @@
Mesh optimized for creating geometry manually.
</brief_description>
<description>
- Mesh optimized for creating geometry manually, similar to OpenGL1.x immediate mode.
+ A mesh type optimized for creating geometry manually, similar to OpenGL 1.x immediate mode.
+ Here's a sample on how to generate a triangular face:
+ [codeblocks]
+ var mesh = ImmediateMesh.new()
+ mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
+ mesh.surface_add_vertex(Vector3.LEFT)
+ mesh.surface_add_vertex(Vector3.FORWARD)
+ mesh.surface_add_vertex(Vector3.ZERO)
+ mesh.surface_end()
+ [/codeblocks]
+ [b]Note:[/b] Generating complex geometries with [ImmediateMesh] is highly inefficient. Instead, it is designed to generate simple geometry that changes often.
</description>
<tutorials>
+ <link title="Using ImmediateMesh">$DOCS_URL/tutorials/3d/procedural_geometry/immediatemesh.html</link>
</tutorials>
<methods>
<method name="clear_surfaces">
diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml
index f5da4553d0..2da5ad3cac 100644
--- a/doc/classes/ItemList.xml
+++ b/doc/classes/ItemList.xml
@@ -341,6 +341,9 @@
<member name="allow_rmb_select" type="bool" setter="set_allow_rmb_select" getter="get_allow_rmb_select" default="false">
If [code]true[/code], right mouse button click can select items.
</member>
+ <member name="allow_search" type="bool" setter="set_allow_search" getter="get_allow_search" default="true">
+ If [code]true[/code], allows navigating the [ItemList] with letter keys through incremental search.
+ </member>
<member name="auto_height" type="bool" setter="set_auto_height" getter="has_auto_height" default="false">
If [code]true[/code], the control will automatically resize the height to fit its content.
</member>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index f63243e8d4..d040c51bb0 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -203,7 +203,7 @@
[b]Note:[/b] [kbd]Backspace[/kbd] is always removing individual composite character components.
</member>
<member name="clear_button_enabled" type="bool" setter="set_clear_button_enabled" getter="is_clear_button_enabled" default="false">
- If [code]true[/code], the [LineEdit] will show a clear button if [code]text[/code] is not empty, which can be used to clear the text quickly.
+ If [code]true[/code], the [LineEdit] will show a clear button if [member text] is not empty, which can be used to clear the text quickly.
</member>
<member name="context_menu_enabled" type="bool" setter="set_context_menu_enabled" getter="is_context_menu_enabled" default="true">
If [code]true[/code], the context menu will appear when right-clicked.
@@ -302,7 +302,7 @@
<signal name="text_change_rejected">
<param index="0" name="rejected_substring" type="String" />
<description>
- Emitted when appending text that overflows the [member max_length]. The appended text is truncated to fit [member max_length], and the part that couldn't fit is passed as the [code]rejected_substring[/code] argument.
+ Emitted when appending text that overflows the [member max_length]. The appended text is truncated to fit [member max_length], and the part that couldn't fit is passed as the [param rejected_substring] argument.
</description>
</signal>
<signal name="text_changed">
@@ -487,7 +487,7 @@
Texture for the clear button. See [member clear_button_enabled].
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- Background used when [LineEdit] has GUI focus. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ Background used when [LineEdit] has GUI focus. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
Default background for the [LineEdit].
diff --git a/doc/classes/Marker2D.xml b/doc/classes/Marker2D.xml
index e4498663fb..16342669c8 100644
--- a/doc/classes/Marker2D.xml
+++ b/doc/classes/Marker2D.xml
@@ -4,7 +4,7 @@
Generic 2D position hint for editing.
</brief_description>
<description>
- Generic 2D position hint for editing. It's just like a plain [Node2D], but it displays as a cross in the 2D editor at all times. You can set cross' visual size by using the gizmo in the 2D editor while the node is selected.
+ Generic 2D position hint for editing. It's just like a plain [Node2D], but it displays as a cross in the 2D editor at all times. You can set the cross' visual size by using the gizmo in the 2D editor while the node is selected.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/MeshDataTool.xml b/doc/classes/MeshDataTool.xml
index 1362949a44..3b208f1fb7 100644
--- a/doc/classes/MeshDataTool.xml
+++ b/doc/classes/MeshDataTool.xml
@@ -19,7 +19,7 @@
vertex += mdt.get_vertex_normal(i)
# Save your change.
mdt.set_vertex(i, vertex)
- mesh.surface_remove(0)
+ mesh.clear_surfaces()
mdt.commit_to_surface(mesh)
var mi = MeshInstance.new()
mi.mesh = mesh
@@ -38,7 +38,7 @@
// Save your change.
mdt.SetVertex(i, vertex);
}
- mesh.SurfaceRemove(0);
+ mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
var mi = new MeshInstance();
mi.Mesh = mesh;
diff --git a/doc/classes/NavigationLink2D.xml b/doc/classes/NavigationLink2D.xml
index 39509e10b7..a08f65da3c 100644
--- a/doc/classes/NavigationLink2D.xml
+++ b/doc/classes/NavigationLink2D.xml
@@ -26,7 +26,7 @@
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_global_end_position">
@@ -48,7 +48,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32.
</description>
</method>
</methods>
@@ -65,7 +65,7 @@
The distance the link will search is controlled by [method NavigationServer2D.map_set_link_connection_radius].
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this link from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this link from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the link belongs to. These navigation layers will be checked when requesting a path with [method NavigationServer2D.map_get_path].
@@ -76,7 +76,7 @@
The distance the link will search is controlled by [method NavigationServer2D.map_set_link_connection_radius].
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves along the link the traveled distance is multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves along the link the traveled distance is multiplied with [member travel_cost] for determining the shortest path.
</member>
</members>
</class>
diff --git a/doc/classes/NavigationLink3D.xml b/doc/classes/NavigationLink3D.xml
index 49fdc774cd..70dfee9c10 100644
--- a/doc/classes/NavigationLink3D.xml
+++ b/doc/classes/NavigationLink3D.xml
@@ -26,7 +26,7 @@
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_global_end_position">
@@ -48,7 +48,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32.
</description>
</method>
</methods>
@@ -65,7 +65,7 @@
The distance the link will search is controlled by [method NavigationServer3D.map_set_link_connection_radius].
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this link from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this link from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the link belongs to. These navigation layers will be checked when requesting a path with [method NavigationServer3D.map_get_path].
@@ -76,7 +76,7 @@
The distance the link will search is controlled by [method NavigationServer3D.map_set_link_connection_radius].
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves along the link the traveled distance is multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves along the link the traveled distance is multiplied with [member travel_cost] for determining the shortest path.
</member>
</members>
</class>
diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml
index 75d648c540..e96f3dc7e4 100644
--- a/doc/classes/NavigationServer2D.xml
+++ b/doc/classes/NavigationServer2D.xml
@@ -148,7 +148,7 @@
<return type="Vector2" />
<param index="0" name="link" type="RID" />
<description>
- Returns the ending position of this [code]link[/code].
+ Returns the ending position of this [param link].
</description>
</method>
<method name="link_get_enter_cost" qualifiers="const">
@@ -162,14 +162,14 @@
<return type="RID" />
<param index="0" name="link" type="RID" />
<description>
- Returns the navigation map [RID] the requested [code]link[/code] is currently assigned to.
+ Returns the navigation map [RID] the requested [param link] is currently assigned to.
</description>
</method>
<method name="link_get_navigation_layers" qualifiers="const">
<return type="int" />
<param index="0" name="link" type="RID" />
<description>
- Returns the navigation layers for this [code]link[/code].
+ Returns the navigation layers for this [param link].
</description>
</method>
<method name="link_get_owner_id" qualifiers="const">
@@ -183,7 +183,7 @@
<return type="Vector2" />
<param index="0" name="link" type="RID" />
<description>
- Returns the starting position of this [code]link[/code].
+ Returns the starting position of this [param link].
</description>
</method>
<method name="link_get_travel_cost" qualifiers="const">
@@ -197,7 +197,7 @@
<return type="bool" />
<param index="0" name="link" type="RID" />
<description>
- Returns whether this [code]link[/code] can be travelled in both directions.
+ Returns whether this [param link] can be travelled in both directions.
</description>
</method>
<method name="link_set_bidirectional">
@@ -205,7 +205,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="bidirectional" type="bool" />
<description>
- Sets whether this [code]link[/code] can be travelled in both directions.
+ Sets whether this [param link] can be travelled in both directions.
</description>
</method>
<method name="link_set_end_position">
@@ -213,7 +213,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="position" type="Vector2" />
<description>
- Sets the exit position for the [code]link[/code].
+ Sets the exit position for the [param link].
</description>
</method>
<method name="link_set_enter_cost">
@@ -221,7 +221,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="enter_cost" type="float" />
<description>
- Sets the [code]enter_cost[/code] for this [code]link[/code].
+ Sets the [param enter_cost] for this [param link].
</description>
</method>
<method name="link_set_map">
@@ -253,7 +253,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="position" type="Vector2" />
<description>
- Sets the entry position for this [code]link[/code].
+ Sets the entry position for this [param link].
</description>
</method>
<method name="link_set_travel_cost">
@@ -261,7 +261,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="travel_cost" type="float" />
<description>
- Sets the [code]travel_cost[/code] for this [code]link[/code].
+ Sets the [param travel_cost] for this [param link].
</description>
</method>
<method name="map_create">
@@ -328,7 +328,7 @@
<return type="RID[]" />
<param index="0" name="map" type="RID" />
<description>
- Returns all navigation link [RID]s that are currently assigned to the requested navigation [code]map[/code].
+ Returns all navigation link [RID]s that are currently assigned to the requested navigation [param map].
</description>
</method>
<method name="map_get_path" qualifiers="const">
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index e6eed172f2..5b79355b61 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -155,7 +155,7 @@
<return type="Vector3" />
<param index="0" name="link" type="RID" />
<description>
- Returns the ending position of this [code]link[/code].
+ Returns the ending position of this [param link].
</description>
</method>
<method name="link_get_enter_cost" qualifiers="const">
@@ -169,14 +169,14 @@
<return type="RID" />
<param index="0" name="link" type="RID" />
<description>
- Returns the navigation map [RID] the requested [code]link[/code] is currently assigned to.
+ Returns the navigation map [RID] the requested [param link] is currently assigned to.
</description>
</method>
<method name="link_get_navigation_layers" qualifiers="const">
<return type="int" />
<param index="0" name="link" type="RID" />
<description>
- Returns the navigation layers for this [code]link[/code].
+ Returns the navigation layers for this [param link].
</description>
</method>
<method name="link_get_owner_id" qualifiers="const">
@@ -190,7 +190,7 @@
<return type="Vector3" />
<param index="0" name="link" type="RID" />
<description>
- Returns the starting position of this [code]link[/code].
+ Returns the starting position of this [param link].
</description>
</method>
<method name="link_get_travel_cost" qualifiers="const">
@@ -204,7 +204,7 @@
<return type="bool" />
<param index="0" name="link" type="RID" />
<description>
- Returns whether this [code]link[/code] can be travelled in both directions.
+ Returns whether this [param link] can be travelled in both directions.
</description>
</method>
<method name="link_set_bidirectional">
@@ -212,7 +212,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="bidirectional" type="bool" />
<description>
- Sets whether this [code]link[/code] can be travelled in both directions.
+ Sets whether this [param link] can be travelled in both directions.
</description>
</method>
<method name="link_set_end_position">
@@ -220,7 +220,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="position" type="Vector3" />
<description>
- Sets the exit position for the [code]link[/code].
+ Sets the exit position for the [param link].
</description>
</method>
<method name="link_set_enter_cost">
@@ -228,7 +228,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="enter_cost" type="float" />
<description>
- Sets the [code]enter_cost[/code] for this [code]link[/code].
+ Sets the [param enter_cost] for this [param link].
</description>
</method>
<method name="link_set_map">
@@ -260,7 +260,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="position" type="Vector3" />
<description>
- Sets the entry position for this [code]link[/code].
+ Sets the entry position for this [param link].
</description>
</method>
<method name="link_set_travel_cost">
@@ -268,7 +268,7 @@
<param index="0" name="link" type="RID" />
<param index="1" name="travel_cost" type="float" />
<description>
- Sets the [code]travel_cost[/code] for this [code]link[/code].
+ Sets the [param travel_cost] for this [param link].
</description>
</method>
<method name="map_create">
@@ -353,7 +353,7 @@
<return type="RID[]" />
<param index="0" name="map" type="RID" />
<description>
- Returns all navigation link [RID]s that are currently assigned to the requested navigation [code]map[/code].
+ Returns all navigation link [RID]s that are currently assigned to the requested navigation [param map].
</description>
</method>
<method name="map_get_path" qualifiers="const">
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index a3f23fa7ae..431ccf634c 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -181,7 +181,7 @@
<param index="0" name="max_output_size" type="int" />
<param index="1" name="compression_mode" type="int" default="0" />
<description>
- Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts gzip and deflate compression modes.[/b]
+ Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b]
This method is potentially slower than [code]decompress[/code], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [code]decompress[/code] knows it's output buffer size from the beginning.
GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.
</description>
diff --git a/doc/classes/PathFollow3D.xml b/doc/classes/PathFollow3D.xml
index bf8194a7cf..41727a7bd8 100644
--- a/doc/classes/PathFollow3D.xml
+++ b/doc/classes/PathFollow3D.xml
@@ -15,7 +15,7 @@
<param index="0" name="transform" type="Transform3D" />
<param index="1" name="rotation_mode" type="int" enum="PathFollow3D.RotationMode" />
<description>
- Correct the [code]transform[/code]. [code]rotation_mode[/code] implicitly specifies how posture (forward, up and sideway direction) is calculated.
+ Correct the [param transform]. [param rotation_mode] implicitly specifies how posture (forward, up and sideway direction) is calculated.
</description>
</method>
</methods>
diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml
index 010a73a8e5..4ed65e7bfb 100644
--- a/doc/classes/PhysicsServer2D.xml
+++ b/doc/classes/PhysicsServer2D.xml
@@ -595,7 +595,7 @@
Sets the function used to calculate physics for the body, if that body allows it (see [method body_set_omit_force_integration]).
The force integration function takes the following two parameters:
1. a [PhysicsDirectBodyState2D] [code]state[/code]: used to retrieve and modify the body's state,
- 2. a [Variant] [code]userdata[/code]: optional user data.
+ 2. a [Variant] [param userdata]: optional user data.
[b]Note:[/b] This callback is currently not called in Godot Physics.
</description>
</method>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 6eed8434ac..949253c998 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -20,9 +20,9 @@
<param index="0" name="hint" type="Dictionary" />
<description>
Adds a custom property info to a property. The dictionary must contain:
- - [code]name[/code]: [String] (the property's name)
- - [code]type[/code]: [int] (see [enum Variant.Type])
- - optionally [code]hint[/code]: [int] (see [enum PropertyHint]) and [code]hint_string[/code]: [String]
+ - [code]"name"[/code]: [String] (the property's name)
+ - [code]"type"[/code]: [int] (see [enum Variant.Type])
+ - optionally [code]"hint"[/code]: [int] (see [enum PropertyHint]) and [code]"hint_string"[/code]: [String]
[b]Example:[/b]
[codeblocks]
[gdscript]
@@ -456,6 +456,9 @@
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
</member>
+ <member name="debug/gdscript/warnings/redundant_static_unload" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@static_unload[/code] annotation is used in a script without any static variables.
+ </member>
<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
</member>
diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml
index 143c199cac..c402040c65 100644
--- a/doc/classes/Rect2.xml
+++ b/doc/classes/Rect2.xml
@@ -154,7 +154,7 @@
<param index="0" name="b" type="Rect2" />
<param index="1" name="include_borders" type="bool" default="false" />
<description>
- Returns [code]true[/code] if the [Rect2] overlaps with [code]b[/code] (i.e. they have at least one point in common).
+ Returns [code]true[/code] if the [Rect2] overlaps with [param b] (i.e. they have at least one point in common).
If [param include_borders] is [code]true[/code], they will also be considered overlapping if their borders touch, even without intersection.
</description>
</method>
diff --git a/doc/classes/Rect2i.xml b/doc/classes/Rect2i.xml
index 44c7345edb..ba20bb60e6 100644
--- a/doc/classes/Rect2i.xml
+++ b/doc/classes/Rect2i.xml
@@ -143,7 +143,7 @@
<return type="Rect2i" />
<param index="0" name="b" type="Rect2i" />
<description>
- Returns the intersection of this [Rect2i] and [code]b[/code].
+ Returns the intersection of this [Rect2i] and [param b].
If the rectangles do not intersect, an empty [Rect2i] is returned.
</description>
</method>
@@ -151,7 +151,7 @@
<return type="bool" />
<param index="0" name="b" type="Rect2i" />
<description>
- Returns [code]true[/code] if the [Rect2i] overlaps with [code]b[/code] (i.e. they have at least one point in common).
+ Returns [code]true[/code] if the [Rect2i] overlaps with [param b] (i.e. they have at least one point in common).
</description>
</method>
<method name="merge" qualifiers="const">
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index f597cb7efc..b9f6a009fb 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -452,6 +452,14 @@
<description>
</description>
</method>
+ <method name="sampler_is_format_supported_for_filter" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="format" type="int" enum="RenderingDevice.DataFormat" />
+ <param index="1" name="sampler_filter" type="int" enum="RenderingDevice.SamplerFilter" />
+ <description>
+ Returns [code]true[/code] if implementation supports using a texture of [param format] with the given [param sampler_filter].
+ </description>
+ </method>
<method name="screen_get_framebuffer_format" qualifiers="const">
<return type="int" />
<description>
diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml
index 38334b1348..24372e51e0 100644
--- a/doc/classes/RigidBody2D.xml
+++ b/doc/classes/RigidBody2D.xml
@@ -283,7 +283,7 @@
Kinematic body freeze mode. Similar to [constant FREEZE_MODE_STATIC], but collides with other bodies along its path when moved. Useful for a frozen body that needs to be animated.
</constant>
<constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode">
- In this mode, the body's center of mass is calculated automatically based on its shapes.
+ In this mode, the body's center of mass is calculated automatically based on its shapes. This assumes that the shapes' origins are also their center of mass.
</constant>
<constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode">
In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position.
diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml
index f087f912bc..3bd660d8dd 100644
--- a/doc/classes/RigidBody3D.xml
+++ b/doc/classes/RigidBody3D.xml
@@ -290,7 +290,7 @@
Kinematic body freeze mode. Similar to [constant FREEZE_MODE_STATIC], but collides with other bodies along its path when moved. Useful for a frozen body that needs to be animated.
</constant>
<constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode">
- In this mode, the body's center of mass is calculated automatically based on its shapes.
+ In this mode, the body's center of mass is calculated automatically based on its shapes. This assumes that the shapes' origins are also their center of mass.
</constant>
<constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode">
In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position.
diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml
index 2921117b8b..ce793deec1 100644
--- a/doc/classes/SceneTree.xml
+++ b/doc/classes/SceneTree.xml
@@ -62,9 +62,9 @@
<param index="3" name="ignore_time_scale" type="bool" default="false" />
<description>
Returns a [SceneTreeTimer] which will [signal SceneTreeTimer.timeout] after the given time in seconds elapsed in this [SceneTree].
- If [code]process_always[/code] is set to [code]false[/code], pausing the [SceneTree] will also pause the timer.
- If [code]process_in_physics[/code] is set to [code]true[/code], will update the [SceneTreeTimer] during the physics frame instead of the process frame (fixed framerate processing).
- If [code]ignore_time_scale[/code] is set to [code]true[/code], will ignore [member Engine.time_scale] and update the [SceneTreeTimer] with the actual frame delta.
+ If [param process_always] is set to [code]false[/code], pausing the [SceneTree] will also pause the timer.
+ If [param process_in_physics] is set to [code]true[/code], will update the [SceneTreeTimer] during the physics frame instead of the process frame (fixed framerate processing).
+ If [param ignore_time_scale] is set to [code]true[/code], will ignore [member Engine.time_scale] and update the [SceneTreeTimer] with the actual frame delta.
Commonly used to create a one-shot delay timer as in the following example:
[codeblocks]
[gdscript]
diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml
index 58cf70df6a..0fb8a0d44a 100644
--- a/doc/classes/SkeletonIK3D.xml
+++ b/doc/classes/SkeletonIK3D.xml
@@ -4,7 +4,7 @@
SkeletonIK3D is used to place the end bone of a [Skeleton3D] bone chain at a certain point in 3D by rotating all bones in the chain accordingly.
</brief_description>
<description>
- SkeletonIK3D is used to place the end bone of a [Skeleton3D] bone chain at a certain point in 3D by rotating all bones in the chain accordingly. A typical scenario for IK in games is to place a characters feet on the ground or a characters hands on a currently hold object. SkeletonIK uses FabrikInverseKinematic internally to solve the bone chain and applies the results to the [Skeleton3D] [code]bones_global_pose_override[/code] property for all affected bones in the chain. If fully applied this overwrites any bone transform from [Animation]s or bone custom poses set by users. The applied amount can be controlled with the [code]interpolation[/code] property.
+ SkeletonIK3D is used to place the end bone of a [Skeleton3D] bone chain at a certain point in 3D by rotating all bones in the chain accordingly. A typical scenario for IK in games is to place a characters feet on the ground or a characters hands on a currently hold object. SkeletonIK uses FabrikInverseKinematic internally to solve the bone chain and applies the results to the [Skeleton3D] [code]bones_global_pose_override[/code] property for all affected bones in the chain. If fully applied this overwrites any bone transform from [Animation]s or bone custom poses set by users. The applied amount can be controlled with the [member interpolation] property.
[codeblock]
# Apply IK effect automatically on every new frame (not the current)
skeleton_ik_node.start()
@@ -45,7 +45,7 @@
<return type="void" />
<param index="0" name="one_time" type="bool" default="false" />
<description>
- Starts applying IK effects on each frame to the [Skeleton3D] bones but will only take effect starting on the next frame. If [code]one_time[/code] is [code]true[/code], this will take effect immediately but also reset on the next frame.
+ Starts applying IK effects on each frame to the [Skeleton3D] bones but will only take effect starting on the next frame. If [param one_time] is [code]true[/code], this will take effect immediately but also reset on the next frame.
</description>
</method>
<method name="stop">
diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml
index f6935779d6..bbf51114bd 100644
--- a/doc/classes/SoftBody3D.xml
+++ b/doc/classes/SoftBody3D.xml
@@ -69,7 +69,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_collision_mask_value">
@@ -77,7 +77,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_point_pinned">
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index a1552982ae..bf66d27ac6 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -867,7 +867,7 @@
<description>
Moves the caret to the specified [param line] index.
If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs.
- If [param can_be_hidden] is [code]true[/code], the specified [code]line[/code] can be hidden.
+ If [param can_be_hidden] is [code]true[/code], the specified [param line] can be hidden.
[b]Note:[/b] If supporting multiple carets this will not check for any overlap. See [method merge_overlapping_carets].
</description>
</method>
@@ -1061,7 +1061,7 @@
<return type="void" />
<param index="0" name="action" type="int" enum="TextEdit.EditAction" />
<description>
- Starts an action, will end the current action if [code]action[/code] is different.
+ Starts an action, will end the current action if [param action] is different.
An action will also end after a call to [method end_action], after [member ProjectSettings.gui/timers/text_edit_idle_detect_sec] is triggered or a new undoable step outside the [method start_action] and [method end_action] calls.
</description>
</method>
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index 2dd6a1a54e..d4ba3ebb1f 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -134,6 +134,13 @@
Returns the coordinates of the tile for given physics body RID. Such RID can be retrieved from [method KinematicCollision2D.get_collider_rid], when colliding with a tile.
</description>
</method>
+ <method name="get_layer_for_body_rid">
+ <return type="int" />
+ <param index="0" name="body" type="RID" />
+ <description>
+ Returns the tilemap layer of the tile for given physics body RID. Such RID can be retrieved from [method KinematicCollision2D.get_collider_rid], when colliding with a tile.
+ </description>
+ </method>
<method name="get_layer_modulate" qualifiers="const">
<return type="Color" />
<param index="0" name="layer" type="int" />
@@ -303,7 +310,7 @@
<description>
Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions.
If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
- [b]Note:[/b] To work correctly, [code]set_cells_terrain_connect[/code] requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
+ [b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
</method>
<method name="set_cells_terrain_path">
@@ -316,7 +323,7 @@
<description>
Update all the cells in the [param path] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. The function will also connect two successive cell in the path with the same terrain. This function might update neighboring tiles if needed to create correct terrain transitions.
If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
- [b]Note:[/b] To work correctly, [code]set_cells_terrain_path[/code] requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
+ [b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
</method>
<method name="set_layer_enabled">
@@ -334,7 +341,7 @@
<param index="1" name="modulate" type="Color" />
<description>
Sets a layer's color. It will be multiplied by tile's color and TileMap's modulate.
- If [code]layer[/code] is negative, the layers are accessed from the last one.
+ If [param layer] is negative, the layers are accessed from the last one.
</description>
</method>
<method name="set_layer_name">
@@ -343,7 +350,7 @@
<param index="1" name="name" type="String" />
<description>
Sets a layer's name. This is mostly useful in the editor.
- If [code]layer[/code] is negative, the layers are accessed from the last one.
+ If [param layer] is negative, the layers are accessed from the last one.
</description>
</method>
<method name="set_layer_y_sort_enabled">
@@ -353,7 +360,7 @@
<description>
Enables or disables a layer's Y-sorting. If a layer is Y-sorted, the layer will behave as a CanvasItem node where each of its tile gets Y-sorted.
Y-sorted layers should usually be on different Z-index values than not Y-sorted layers, otherwise, each of those layer will be Y-sorted as whole with the Y-sorted one. This is usually an undesired behavior.
- If [code]layer[/code] is negative, the layers are accessed from the last one.
+ If [param layer] is negative, the layers are accessed from the last one.
</description>
</method>
<method name="set_layer_y_sort_origin">
@@ -363,7 +370,7 @@
<description>
Sets a layer's Y-sort origin value. This Y-sort origin value is added to each tile's Y-sort origin value.
This allows, for example, to fake a different height level on each layer. This can be useful for top-down view games.
- If [code]layer[/code] is negative, the layers are accessed from the last one.
+ If [param layer] is negative, the layers are accessed from the last one.
</description>
</method>
<method name="set_layer_z_index">
@@ -372,7 +379,7 @@
<param index="1" name="z_index" type="int" />
<description>
Sets a layers Z-index value. This Z-index is added to each tile's Z-index value.
- If [code]layer[/code] is negative, the layers are accessed from the last one.
+ If [param layer] is negative, the layers are accessed from the last one.
</description>
</method>
<method name="set_navigation_map">
@@ -401,7 +408,7 @@
</member>
<member name="collision_animatable" type="bool" setter="set_collision_animatable" getter="is_collision_animatable" default="false">
If enabled, the TileMap will see its collisions synced to the physics tick and change its collision type from static to kinematic. This is required to create TileMap-based moving platform.
- [b]Note:[/b] Enabling [code]collision_animatable[/code] may have a small performance impact, only do it if the TileMap is moving and has colliding tiles.
+ [b]Note:[/b] Enabling [member collision_animatable] may have a small performance impact, only do it if the TileMap is moving and has colliding tiles.
</member>
<member name="collision_visibility_mode" type="int" setter="set_collision_visibility_mode" getter="get_collision_visibility_mode" enum="TileMap.VisibilityMode" default="0">
Show or hide the TileMap's collision shapes. If set to [constant VISIBILITY_MODE_DEFAULT], this depends on the show collision debug settings.
diff --git a/doc/classes/Timer.xml b/doc/classes/Timer.xml
index 1205f1627c..2f76f0d27a 100644
--- a/doc/classes/Timer.xml
+++ b/doc/classes/Timer.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
Counts down a specified interval and emits a signal on reaching 0. Can be set to repeat or "one-shot" mode.
+ [b]Note:[/b] Timers are affected by [member Engine.time_scale], a higher scale means quicker timeouts, and vice versa.
[b]Note:[/b] To create a one-shot timer without instantiating a node, use [method SceneTree.create_timer].
</description>
<tutorials>
@@ -52,7 +53,7 @@
</member>
<member name="wait_time" type="float" setter="set_wait_time" getter="get_wait_time" default="1.0">
The wait time in seconds.
- [b]Note:[/b] Timers can only emit once per rendered frame at most (or once per physics frame if [member process_callback] is [constant TIMER_PROCESS_PHYSICS]). This means very low wait times (lower than 0.05 seconds) will behave in significantly different ways depending on the rendered framerate. For very low wait times, it is recommended to use a process loop in a script instead of using a Timer node.
+ [b]Note:[/b] Timers can only emit once per rendered frame at most (or once per physics frame if [member process_callback] is [constant TIMER_PROCESS_PHYSICS]). This means very low wait times (lower than 0.05 seconds) will behave in significantly different ways depending on the rendered framerate. For very low wait times, it is recommended to use a process loop in a script instead of using a Timer node. Timers are affected by [member Engine.time_scale], a higher scale means quicker timeouts, and vice versa.
</member>
</members>
<signals>
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 1e4282cd0b..2a3cba3a44 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -335,6 +335,9 @@
<member name="allow_rmb_select" type="bool" setter="set_allow_rmb_select" getter="get_allow_rmb_select" default="false">
If [code]true[/code], a right mouse button click can select items.
</member>
+ <member name="allow_search" type="bool" setter="set_allow_search" getter="get_allow_search" default="true">
+ If [code]true[/code], allows navigating the [Tree] with letter keys through incremental search.
+ </member>
<member name="clip_contents" type="bool" setter="set_clip_contents" getter="is_clipping_contents" overrides="Control" default="true" />
<member name="column_titles_visible" type="bool" setter="set_column_titles_visible" getter="are_column_titles_visible" default="false">
If [code]true[/code], column titles are visible.
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 49b4622aed..b97b4bf17d 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -350,6 +350,13 @@
<description>
</description>
</method>
+ <method name="is_edit_multiline" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="column" type="int" />
+ <description>
+ Returns [code]true[/code] if the given [param column] is multiline editable.
+ </description>
+ </method>
<method name="is_editable">
<return type="bool" />
<param index="0" name="column" type="int" />
@@ -456,7 +463,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="checked" type="bool" />
<description>
- If [code]true[/code], the given [param column] is checked. Clears column's indeterminate status.
+ If [param checked] is [code]true[/code], the given [param column] is checked. Clears column's indeterminate status.
</description>
</method>
<method name="set_collapsed_recursive">
@@ -516,12 +523,21 @@
Sets custom font size used to draw text in the given [param column].
</description>
</method>
+ <method name="set_edit_multiline">
+ <return type="void" />
+ <param index="0" name="column" type="int" />
+ <param index="1" name="multiline" type="bool" />
+ <description>
+ If [param multiline] is [code]true[/code], the given [param column] is multiline editable.
+ [b]Note:[/b] This option only affects the type of control ([LineEdit] or [TextEdit]) that appears when editing the column. You can set multiline values with [method set_text] even if the column is not multiline editable.
+ </description>
+ </method>
<method name="set_editable">
<return type="void" />
<param index="0" name="column" type="int" />
<param index="1" name="enabled" type="bool" />
<description>
- If [code]true[/code], the given [param column] is editable.
+ If [param enabled] is [code]true[/code], the given [param column] is editable.
</description>
</method>
<method name="set_expand_right">
@@ -529,7 +545,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="enable" type="bool" />
<description>
- If [code]true[/code], the given [param column] is expanded to the right.
+ If [param enable] is [code]true[/code], the given [param column] is expanded to the right.
</description>
</method>
<method name="set_icon">
@@ -569,7 +585,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="indeterminate" type="bool" />
<description>
- If [code]true[/code], the given [param column] is marked [param indeterminate].
+ If [param indeterminate] is [code]true[/code], the given [param column] is marked indeterminate.
[b]Note:[/b] If set [code]true[/code] from [code]false[/code], then column is cleared of checked status.
</description>
</method>
@@ -614,7 +630,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="selectable" type="bool" />
<description>
- If [code]true[/code], the given column is selectable.
+ If [param selectable] is [code]true[/code], the given [param column] is selectable.
</description>
</method>
<method name="set_structured_text_bidi_override">
diff --git a/doc/classes/VSlider.xml b/doc/classes/VSlider.xml
index 4a203d2443..b1906ccef2 100644
--- a/doc/classes/VSlider.xml
+++ b/doc/classes/VSlider.xml
@@ -14,6 +14,9 @@
<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" default="1" />
</members>
<theme_items>
+ <theme_item name="center_grabber" data_type="constant" type="int" default="0">
+ Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position.
+ </theme_item>
<theme_item name="grabber_offset" data_type="constant" type="int" default="0">
Horizontal offset of the grabber.
</theme_item>
diff --git a/doc/classes/VideoStreamPlayback.xml b/doc/classes/VideoStreamPlayback.xml
index a81d011e9d..be3b0d4950 100644
--- a/doc/classes/VideoStreamPlayback.xml
+++ b/doc/classes/VideoStreamPlayback.xml
@@ -61,21 +61,21 @@
<return type="void" />
<param index="0" name="time" type="float" />
<description>
- Seeks to [code]time[/code] seconds. Called in response to the [member VideoStreamPlayer.stream_position] setter.
+ Seeks to [param time] seconds. Called in response to the [member VideoStreamPlayer.stream_position] setter.
</description>
</method>
<method name="_set_audio_track" qualifiers="virtual">
<return type="void" />
<param index="0" name="idx" type="int" />
<description>
- Select the audio track [code]idx[/code]. Called when playback starts, and in response to the [member VideoStreamPlayer.audio_track] setter.
+ Select the audio track [param idx]. Called when playback starts, and in response to the [member VideoStreamPlayer.audio_track] setter.
</description>
</method>
<method name="_set_paused" qualifiers="virtual">
<return type="void" />
<param index="0" name="paused" type="bool" />
<description>
- Set the paused status of video playback. [method _is_paused] must return [code]paused[/code]. Called in response to the [member VideoStreamPlayer.paused] setter.
+ Set the paused status of video playback. [method _is_paused] must return [param paused]. Called in response to the [member VideoStreamPlayer.paused] setter.
</description>
</method>
<method name="_stop" qualifiers="virtual">
@@ -88,7 +88,7 @@
<return type="void" />
<param index="0" name="delta" type="float" />
<description>
- Ticks video playback for [code]delta[/code] seconds. Called every frame as long as [method _is_paused] and [method _is_playing] return true.
+ Ticks video playback for [param delta] seconds. Called every frame as long as [method _is_paused] and [method _is_playing] return true.
</description>
</method>
<method name="mix_audio">
@@ -97,7 +97,7 @@
<param index="1" name="buffer" type="PackedFloat32Array" default="PackedFloat32Array()" />
<param index="2" name="offset" type="int" default="0" />
<description>
- Render [code]num_frames[/code] audio frames (of [method _get_channels] floats each) from [code]buffer[/code], starting from index [code]offset[/code] in the array. Returns the number of audio frames rendered, or -1 on error.
+ Render [param num_frames] audio frames (of [method _get_channels] floats each) from [param buffer], starting from index [param offset] in the array. Returns the number of audio frames rendered, or -1 on error.
</description>
</method>
</methods>
diff --git a/doc/classes/VisualInstance3D.xml b/doc/classes/VisualInstance3D.xml
index c563c0e014..6e7042431b 100644
--- a/doc/classes/VisualInstance3D.xml
+++ b/doc/classes/VisualInstance3D.xml
@@ -36,7 +36,7 @@
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member layers] is enabled, given a [code]layer_number[/code] between 1 and 20.
+ Returns whether or not the specified layer of the [member layers] is enabled, given a [param layer_number] between 1 and 20.
</description>
</method>
<method name="set_base">
diff --git a/doc/classes/VoxelGI.xml b/doc/classes/VoxelGI.xml
index d72139d483..a019eac910 100644
--- a/doc/classes/VoxelGI.xml
+++ b/doc/classes/VoxelGI.xml
@@ -20,7 +20,7 @@
<param index="0" name="from_node" type="Node" default="null" />
<param index="1" name="create_visual_debug" type="bool" default="false" />
<description>
- Bakes the effect from all [GeometryInstance3D]s marked with [constant GeometryInstance3D.GI_MODE_STATIC] and [Light3D]s marked with either [constant Light3D.BAKE_STATIC] or [constant Light3D.BAKE_DYNAMIC]. If [code]create_visual_debug[/code] is [code]true[/code], after baking the light, this will generate a [MultiMesh] that has a cube representing each solid cell with each cube colored to the cell's albedo color. This can be used to visualize the [VoxelGI]'s data and debug any issues that may be occurring.
+ Bakes the effect from all [GeometryInstance3D]s marked with [constant GeometryInstance3D.GI_MODE_STATIC] and [Light3D]s marked with either [constant Light3D.BAKE_STATIC] or [constant Light3D.BAKE_DYNAMIC]. If [param create_visual_debug] is [code]true[/code], after baking the light, this will generate a [MultiMesh] that has a cube representing each solid cell with each cube colored to the cell's albedo color. This can be used to visualize the [VoxelGI]'s data and debug any issues that may be occurring.
[b]Note:[/b] [method bake] works from the editor and in exported projects. This makes it suitable for procedurally generated or user-built levels. Baking a [VoxelGI] node generally takes from 5 to 20 seconds in most scenes. Reducing [member subdiv] can speed up baking.
[b]Note:[/b] [GeometryInstance3D]s and [Light3D]s must be fully ready before [method bake] is called. If you are procedurally creating those and some meshes or lights are missing from your baked [VoxelGI], use [code]call_deferred("bake")[/code] instead of calling [method bake] directly.
</description>
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 87fef49aac..4cc48bd70b 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -348,16 +348,14 @@
<return type="void" />
<param index="0" name="rect" type="Rect2i" default="Rect2i(0, 0, 0, 0)" />
<description>
- Shows the [Window] and makes it transient (see [member transient]). If [param rect] is provided, it will be set as the [Window]'s size.
- Fails if called on the main window.
+ Shows the [Window] and makes it transient (see [member transient]). If [param rect] is provided, it will be set as the [Window]'s size. Fails if called on the main window.
</description>
</method>
<method name="popup_centered">
<return type="void" />
<param index="0" name="minsize" type="Vector2i" default="Vector2i(0, 0)" />
<description>
- Popups the [Window] at the center of the current screen, with optionally given minimum size.
- If the [Window] is embedded, it will be centered in the parent [Viewport] instead.
+ Popups the [Window] at the center of the current screen, with optionally given minimum size. If the [Window] is embedded, it will be centered in the parent [Viewport] instead.
[b]Note:[/b] Calling it with the default value of [param minsize] is equivalent to calling it with [member size].
</description>
</method>
@@ -366,8 +364,7 @@
<param index="0" name="minsize" type="Vector2i" default="Vector2i(0, 0)" />
<param index="1" name="fallback_ratio" type="float" default="0.75" />
<description>
- Popups the [Window] centered inside its parent [Window].
- [code]fallback_ratio[/code] determines the maximum size of the [Window], in relation to its parent.
+ Popups the [Window] centered inside its parent [Window]. [param fallback_ratio] determines the maximum size of the [Window], in relation to its parent.
[b]Note:[/b] Calling it with the default value of [param minsize] is equivalent to calling it with [member size].
</description>
</method>
@@ -382,8 +379,7 @@
<return type="void" />
<param index="0" name="parent_rect" type="Rect2i" />
<description>
- Popups the [Window] with a position shifted by parent [Window]'s position.
- If the [Window] is embedded, has the same effect as [method popup].
+ Popups the [Window] with a position shifted by parent [Window]'s position. If the [Window] is embedded, has the same effect as [method popup].
</description>
</method>
<method name="remove_theme_color_override">
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 8a8f421f4d..2594fa8cbd 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -86,7 +86,6 @@ CLASS_GROUPS_BASE: Dict[str, str] = {
}
# Sync with editor\register_editor_types.cpp
EDITOR_CLASSES: List[str] = [
- "AnimationTrackEditPlugin",
"FileSystemDock",
"ScriptCreateDialog",
"ScriptEditor",
@@ -1679,6 +1678,26 @@ def format_text_block(
inside_code_tag = cmd
escape_pre = True
+ valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
+ if valid_context:
+ endcode_pos = text.find("[/code]", endq_pos + 1)
+ if endcode_pos == -1:
+ print_error(
+ f"{state.current_class}.xml: Tag depth mismatch for [code]: no closing [/code] in {context_name}.",
+ state,
+ )
+ break
+
+ inside_code_text = text[endq_pos + 1 : endcode_pos]
+ context_params: List[ParameterDef] = context.parameters # type: ignore
+ for param_def in context_params:
+ if param_def.name == inside_code_text:
+ print_warning(
+ f'{state.current_class}.xml: Potential error inside of a code tag, found a string "{inside_code_text}" that matches one of the parameters in {context_name}.',
+ state,
+ )
+ break
+
# Cross-references to items in this or other class documentation pages.
elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS):
link_type: str = ""
diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp
index 6095fef035..e6e3af0928 100644
--- a/drivers/alsa/audio_driver_alsa.cpp
+++ b/drivers/alsa/audio_driver_alsa.cpp
@@ -44,7 +44,8 @@ extern int initialize_pulse(int verbose);
#endif
Error AudioDriverALSA::init_output_device() {
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
+
speaker_mode = SPEAKER_MODE_STEREO;
channels = 2;
diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp
index 2c959bb07b..4011727433 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.cpp
+++ b/drivers/coreaudio/audio_driver_coreaudio.cpp
@@ -116,7 +116,7 @@ Error AudioDriverCoreAudio::init() {
break;
}
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
memset(&strdesc, 0, sizeof(strdesc));
strdesc.mFormatID = kAudioFormatLinearPCM;
@@ -405,7 +405,7 @@ Error AudioDriverCoreAudio::init_input_device() {
break;
}
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
memset(&strdesc, 0, sizeof(strdesc));
strdesc.mFormatID = kAudioFormatLinearPCM;
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
index 9ae74a8906..88fda35a3b 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
@@ -305,7 +305,7 @@ Error AudioDriverPulseAudio::init() {
active.clear();
exit_thread.clear();
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
pa_ml = pa_mainloop_new();
ERR_FAIL_COND_V(pa_ml == nullptr, ERR_CANT_OPEN);
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index d03324527e..2fd46d65ff 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -4305,6 +4305,18 @@ RID RenderingDeviceVulkan::sampler_create(const SamplerState &p_state) {
return id;
}
+bool RenderingDeviceVulkan::sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_sampler_filter) const {
+ ERR_FAIL_INDEX_V(p_format, DATA_FORMAT_MAX, false);
+
+ _THREAD_SAFE_METHOD_
+
+ // Validate that this image is supported for the intended filtering.
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(context->get_physical_device(), vulkan_formats[p_format], &properties);
+
+ return p_sampler_filter == RD::SAMPLER_FILTER_NEAREST || (p_sampler_filter == RD::SAMPLER_FILTER_LINEAR && (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT));
+}
+
/**********************/
/**** VERTEX ARRAY ****/
/**********************/
diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h
index 3f01895745..2ec1574955 100644
--- a/drivers/vulkan/rendering_device_vulkan.h
+++ b/drivers/vulkan/rendering_device_vulkan.h
@@ -1084,6 +1084,7 @@ public:
/*****************/
virtual RID sampler_create(const SamplerState &p_state);
+ virtual bool sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_sampler_filter) const;
/**********************/
/**** VERTEX ARRAY ****/
diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp
index 805528b8c7..7d11293f9b 100644
--- a/drivers/wasapi/audio_driver_wasapi.cpp
+++ b/drivers/wasapi/audio_driver_wasapi.cpp
@@ -545,7 +545,7 @@ Error AudioDriverWASAPI::finish_input_device() {
}
Error AudioDriverWASAPI::init() {
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
target_latency_ms = GLOBAL_GET("audio/driver/output_latency");
diff --git a/drivers/xaudio2/audio_driver_xaudio2.cpp b/drivers/xaudio2/audio_driver_xaudio2.cpp
index 44ce01d4d7..2b5f593a07 100644
--- a/drivers/xaudio2/audio_driver_xaudio2.cpp
+++ b/drivers/xaudio2/audio_driver_xaudio2.cpp
@@ -39,7 +39,8 @@ Error AudioDriverXAudio2::init() {
pcm_open = false;
samples_in = nullptr;
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
+
// FIXME: speaker_mode seems unused in the Xaudio2 driver so far
speaker_mode = SPEAKER_MODE_STEREO;
channels = 2;
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
index fc806ded5e..e3686a0217 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
@@ -32,9 +32,8 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
-#include "editor/editor_node.h"
-#include "editor/editor_run_native.h"
#include "editor/export/editor_export_platform.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
void DebugAdapterParser::_bind_methods() {
@@ -162,7 +161,7 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const
Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
- EditorNode::get_singleton()->run_stop();
+ EditorRunBar::get_singleton()->stop_playing();
}
return prepare_success_response(p_params);
@@ -188,7 +187,7 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
String platform_string = args.get("platform", "host");
if (platform_string == "host") {
- EditorNode::get_singleton()->run_play();
+ EditorRunBar::get_singleton()->play_main_scene();
} else {
int device = args.get("device", -1);
int idx = -1;
@@ -212,8 +211,8 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
}
- EditorNode *editor = EditorNode::get_singleton();
- Error err = platform_string == "android" ? editor->run_play_native(device * 10000 + idx) : editor->run_play_native(idx);
+ EditorRunBar *run_bar = EditorRunBar::get_singleton();
+ Error err = platform_string == "android" ? run_bar->start_native_device(device * 10000 + idx) : run_bar->start_native_device(idx);
if (err) {
if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
@@ -257,13 +256,13 @@ Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
}
Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
- EditorNode::get_singleton()->run_stop();
+ EditorRunBar::get_singleton()->stop_playing();
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const {
- EditorNode::get_singleton()->get_pause_button()->set_pressed(true);
+ EditorRunBar::get_singleton()->get_pause_button()->set_pressed(true);
EditorDebuggerNode::get_singleton()->_paused();
DebugAdapterProtocol::get_singleton()->notify_stopped_paused();
@@ -272,7 +271,7 @@ Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const {
}
Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const {
- EditorNode::get_singleton()->get_pause_button()->set_pressed(false);
+ EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false);
EditorDebuggerNode::get_singleton()->_paused();
DebugAdapterProtocol::get_singleton()->notify_continued();
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
index f85163cd6a..26fb73570e 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
@@ -38,6 +38,7 @@
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "editor/gui/editor_run_bar.h"
DebugAdapterProtocol *DebugAdapterProtocol::singleton = nullptr;
@@ -812,7 +813,7 @@ Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array
}
void DebugAdapterProtocol::on_debug_paused() {
- if (EditorNode::get_singleton()->get_pause_button()->is_pressed()) {
+ if (EditorRunBar::get_singleton()->get_pause_button()->is_pressed()) {
notify_stopped_paused();
} else {
notify_continued();
@@ -1017,8 +1018,7 @@ DebugAdapterProtocol::DebugAdapterProtocol() {
reset_ids();
- EditorNode *node = EditorNode::get_singleton();
- node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused));
+ EditorRunBar::get_singleton()->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused));
EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton();
debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled));
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index a368cacf56..7083640b24 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
@@ -87,8 +88,7 @@ EditorDebuggerNode::EditorDebuggerNode() {
remote_scene_tree_timeout = EDITOR_DEF("debugger/remote_scene_tree_refresh_interval", 1.0);
inspect_edited_object_timeout = EDITOR_DEF("debugger/remote_inspect_refresh_interval", 0.2);
- EditorNode *editor = EditorNode::get_singleton();
- editor->get_pause_button()->connect("pressed", callable_mp(this, &EditorDebuggerNode::_paused));
+ EditorRunBar::get_singleton()->get_pause_button()->connect("pressed", callable_mp(this, &EditorDebuggerNode::_paused));
}
ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
@@ -260,7 +260,7 @@ void EditorDebuggerNode::stop(bool p_force) {
server->stop();
EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR);
- if (EditorNode::get_singleton()->is_movie_maker_enabled()) {
+ if (EditorRunBar::get_singleton()->is_movie_maker_enabled()) {
// Request attention in case the user was doing something else when movie recording is finished.
DisplayServer::get_singleton()->window_request_attention();
}
@@ -344,7 +344,7 @@ void EditorDebuggerNode::_notification(int p_what) {
}
}
- EditorNode::get_singleton()->get_pause_button()->set_disabled(false);
+ EditorRunBar::get_singleton()->get_pause_button()->set_disabled(false);
// Switch to remote tree view if so desired.
auto_switch_remote_scene_tree = (bool)EDITOR_GET("debugger/auto_switch_to_remote_scene_tree");
if (auto_switch_remote_scene_tree) {
@@ -413,8 +413,8 @@ void EditorDebuggerNode::_debugger_stopped(int p_id) {
}
});
if (!found) {
- EditorNode::get_singleton()->get_pause_button()->set_pressed(false);
- EditorNode::get_singleton()->get_pause_button()->set_disabled(true);
+ EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false);
+ EditorRunBar::get_singleton()->get_pause_button()->set_disabled(true);
SceneTreeDock::get_singleton()->hide_remote_tree();
SceneTreeDock::get_singleton()->hide_tab_buttons();
EditorNode::get_singleton()->notify_all_debug_sessions_exited();
@@ -509,7 +509,7 @@ void EditorDebuggerNode::_update_debug_options() {
}
void EditorDebuggerNode::_paused() {
- const bool paused = EditorNode::get_singleton()->get_pause_button()->is_pressed();
+ const bool paused = EditorRunBar::get_singleton()->get_pause_button()->is_pressed();
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
if (paused && !dbg->is_breaked()) {
dbg->debug_break();
@@ -527,7 +527,7 @@ void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, String p_mes
tabs->set_current_tab(p_debugger);
}
_break_state_changed();
- EditorNode::get_singleton()->get_pause_button()->set_pressed(p_breaked);
+ EditorRunBar::get_singleton()->get_pause_button()->set_pressed(p_breaked);
emit_signal(SNAME("breaked"), p_breaked, p_can_debug);
}
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index 110fa941af..996e16be33 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -374,6 +374,11 @@ void DocTools::generate(bool p_basic_types) {
classes.pop_front();
continue;
}
+ if (ClassDB::get_api_type(name) != ClassDB::API_CORE && ClassDB::get_api_type(name) != ClassDB::API_EDITOR) {
+ print_verbose(vformat("Class '%s' belongs neither to core nor editor, skipping.", name));
+ classes.pop_front();
+ continue;
+ }
String cname = name;
// Property setters and getters do not get exposed as individual methods.
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 1421db42ec..1f9d754185 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -713,6 +713,16 @@ int EditorData::get_edited_scene() const {
return current_edited_scene;
}
+int EditorData::get_edited_scene_from_path(const String &p_path) const {
+ for (int i = 0; i < edited_scene.size(); i++) {
+ if (edited_scene[i].path == p_path) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
void EditorData::set_edited_scene(int p_idx) {
ERR_FAIL_INDEX(p_idx, edited_scene.size());
current_edited_scene = p_idx;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 370963074c..d4a2f534cd 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -196,9 +196,11 @@ public:
void set_edited_scene(int p_idx);
void set_edited_scene_root(Node *p_root);
int get_edited_scene() const;
+ int get_edited_scene_from_path(const String &p_path) const;
Node *get_edited_scene_root(int p_idx = -1);
int get_edited_scene_count() const;
Vector<EditedScene> get_edited_scenes() const;
+
String get_scene_title(int p_idx, bool p_always_strip_extension = false) const;
String get_scene_path(int p_idx) const;
String get_scene_type(int p_idx) const;
@@ -211,6 +213,7 @@ public:
NodePath get_edited_scene_live_edit_root();
bool check_and_update_scene(int p_idx);
void move_edited_scene_to_index(int p_idx);
+
bool call_build();
void set_scene_as_saved(int p_idx);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index c2801e1188..f2d852cc2b 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -32,9 +32,9 @@
#include "core/core_constants.h"
#include "core/input/input.h"
+#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/version.h"
-#include "core/version_generated.gen.h"
#include "doc_data_compressed.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
@@ -45,6 +45,8 @@
#define CONTRIBUTE_URL vformat("%s/contributing/documentation/updating_the_class_reference.html", VERSION_DOCS_URL)
+// TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe.
+// Might this be a problem?
DocTools *EditorHelp::doc = nullptr;
class DocCache : public Resource {
@@ -74,6 +76,49 @@ public:
void set_classes(const Array &p_classes) { classes = p_classes; }
};
+static bool _attempt_doc_load(const String &p_class) {
+ // Docgen always happens in the outer-most class: it also generates docs for inner classes.
+ String outer_class = p_class.get_slice(".", 0);
+ if (!ScriptServer::is_global_class(outer_class)) {
+ return false;
+ }
+
+ // ResourceLoader is used in order to have a script-agnostic way to load scripts.
+ // This forces GDScript to compile the code, which is unnecessary for docgen, but it's a good compromise right now.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(outer_class), outer_class);
+ if (script.is_valid()) {
+ Vector<DocData::ClassDoc> docs = script->get_documentation();
+ for (int j = 0; j < docs.size(); j++) {
+ const DocData::ClassDoc &doc = docs.get(j);
+ EditorHelp::get_doc_data()->add_doc(doc);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// Removes unnecessary prefix from p_class_specifier when within the p_edited_class context
+static String _contextualize_class_specifier(const String &p_class_specifier, const String &p_edited_class) {
+ // If this is a completely different context than the current class, then keep full path
+ if (!p_class_specifier.begins_with(p_edited_class)) {
+ return p_class_specifier;
+ }
+
+ // Here equal length + begins_with from above implies p_class_specifier == p_edited_class :)
+ if (p_class_specifier.length() == p_edited_class.length()) {
+ int rfind = p_class_specifier.rfind(".");
+ if (rfind == -1) { // Single identifier
+ return p_class_specifier;
+ }
+ // Multiple specifiers: keep last one only
+ return p_class_specifier.substr(rfind + 1);
+ }
+
+ // Remove prefix
+ return p_class_specifier.substr(p_edited_class.length() + 1);
+}
+
void EditorHelp::_update_theme_item_cache() {
VBoxContainer::_update_theme_item_cache();
@@ -131,12 +176,13 @@ void EditorHelp::_class_list_select(const String &p_select) {
}
void EditorHelp::_class_desc_select(const String &p_select) {
- if (p_select.begins_with("$")) { //enum
+ if (p_select.begins_with("$")) { // enum
String select = p_select.substr(1, p_select.length());
String class_name;
- if (select.contains(".")) {
- class_name = select.get_slice(".", 0);
- select = select.get_slice(".", 1);
+ int rfind = select.rfind(".");
+ if (rfind != -1) {
+ class_name = select.substr(0, rfind);
+ select = select.substr(rfind + 1);
} else {
class_name = "@GlobalScope";
}
@@ -254,35 +300,35 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
bool is_enum_type = !p_enum.is_empty();
bool can_ref = !p_type.contains("*") || is_enum_type;
- String t = p_type;
+ String link_t = p_type; // For links in metadata
+ String display_t = link_t; // For display purposes
if (is_enum_type) {
- if (p_enum.get_slice_count(".") > 1) {
- t = p_enum.get_slice(".", 1);
- } else {
- t = p_enum.get_slice(".", 0);
- }
+ link_t = p_enum; // The link for enums is always the full enum description
+ display_t = _contextualize_class_specifier(p_enum, edited_class);
+ } else {
+ display_t = _contextualize_class_specifier(p_type, edited_class);
}
class_desc->push_color(theme_cache.type_color);
bool add_array = false;
if (can_ref) {
- if (t.ends_with("[]")) {
+ if (link_t.ends_with("[]")) {
add_array = true;
- t = t.replace("[]", "");
+ link_t = link_t.replace("[]", "");
- class_desc->push_meta("#Array"); //class
+ class_desc->push_meta("#Array"); // class
class_desc->add_text("Array");
class_desc->pop();
class_desc->add_text("[");
}
if (is_enum_type) {
- class_desc->push_meta("$" + p_enum); //class
+ class_desc->push_meta("$" + link_t); // enum
} else {
- class_desc->push_meta("#" + t); //class
+ class_desc->push_meta("#" + link_t); // class
}
}
- class_desc->add_text(t);
+ class_desc->add_text(display_t);
if (can_ref) {
class_desc->pop(); // Pushed meta above.
if (add_array) {
@@ -339,7 +385,7 @@ String EditorHelp::_fix_constant(const String &p_constant) const {
class_desc->pop();
void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview) {
- method_line[p_method.name] = class_desc->get_paragraph_count() - 2; //gets overridden if description
+ method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description
const bool is_vararg = p_method.qualifiers.contains("vararg");
@@ -353,8 +399,8 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
_add_type(p_method.return_type, p_method.return_enum);
if (p_overview) {
- class_desc->pop(); //align
- class_desc->pop(); //cell
+ class_desc->pop(); // align
+ class_desc->pop(); // cell
class_desc->push_cell();
} else {
class_desc->add_text(" ");
@@ -369,7 +415,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
class_desc->pop();
if (p_overview && !p_method.description.strip_edges().is_empty()) {
- class_desc->pop(); //meta
+ class_desc->pop(); // meta
}
class_desc->push_color(theme_cache.symbol_color);
@@ -448,7 +494,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
}
if (p_overview) {
- class_desc->pop(); //cell
+ class_desc->pop(); // cell
}
}
@@ -489,8 +535,9 @@ void EditorHelp::_pop_code_font() {
class_desc->pop();
}
-Error EditorHelp::_goto_desc(const String &p_class, int p_vscr) {
- if (!doc->class_list.has(p_class)) {
+Error EditorHelp::_goto_desc(const String &p_class) {
+ // If class doesn't have docs listed, attempt on-demand docgen
+ if (!doc->class_list.has(p_class) && !_attempt_doc_load(p_class)) {
return ERR_DOES_NOT_EXIST;
}
@@ -530,9 +577,9 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods)
if (any_previous && !m.is_empty()) {
class_desc->push_cell();
- class_desc->pop(); //cell
+ class_desc->pop(); // cell
class_desc->push_cell();
- class_desc->pop(); //cell
+ class_desc->pop(); // cell
}
String group_prefix;
@@ -550,9 +597,9 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods)
if (is_new_group && pass == 1) {
class_desc->push_cell();
- class_desc->pop(); //cell
+ class_desc->pop(); // cell
class_desc->push_cell();
- class_desc->pop(); //cell
+ class_desc->pop(); // cell
}
_add_method(m[i], true);
@@ -561,7 +608,7 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods)
any_previous = !m.is_empty();
}
- class_desc->pop(); //table
+ class_desc->pop(); // table
class_desc->pop();
_pop_code_font();
@@ -1197,7 +1244,7 @@ void EditorHelp::_update_doc() {
_add_text(cd.signals[i].arguments[j].name);
class_desc->add_text(": ");
- _add_type(cd.signals[i].arguments[j].type);
+ _add_type(cd.signals[i].arguments[j].type, cd.signals[i].arguments[j].enumeration);
if (!cd.signals[i].arguments[j].default_value.is_empty()) {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(" = ");
@@ -1768,7 +1815,6 @@ void EditorHelp::_request_help(const String &p_string) {
if (err == OK) {
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
}
- //100 palabras
}
void EditorHelp::_help_callback(const String &p_topic) {
@@ -2179,7 +2225,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
tag_stack.push_front("font");
} else {
- p_rt->add_text("["); //ignore
+ p_rt->add_text("["); // ignore
pos = brk_pos + 1;
}
}
@@ -2202,16 +2248,18 @@ String EditorHelp::get_cache_full_path() {
}
static bool first_attempt = true;
-static List<StringName> classes_whitelist;
+
+static String _compute_doc_version_hash() {
+ return uitos(ClassDB::get_api_hash(ClassDB::API_CORE)) + "-" + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR));
+}
void EditorHelp::_load_doc_thread(void *p_udata) {
DEV_ASSERT(first_attempt);
Ref<DocCache> cache_res = ResourceLoader::load(get_cache_full_path());
- if (cache_res.is_valid() && cache_res->get_version_hash() == String(VERSION_HASH)) {
+ if (cache_res.is_valid() && cache_res->get_version_hash() == _compute_doc_version_hash()) {
for (int i = 0; i < cache_res->get_classes().size(); i++) {
doc->add_doc(DocData::ClassDoc::from_dict(cache_res->get_classes()[i]));
}
- classes_whitelist.clear();
} else {
// We have to go back to the main thread to start from scratch.
first_attempt = false;
@@ -2226,7 +2274,7 @@ void EditorHelp::_gen_doc_thread(void *p_udata) {
Ref<DocCache> cache_res;
cache_res.instantiate();
- cache_res->set_version_hash(VERSION_HASH);
+ cache_res->set_version_hash(_compute_doc_version_hash());
Array classes;
for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
classes.push_back(DocData::ClassDoc::to_dict(E.value));
@@ -2249,9 +2297,6 @@ void EditorHelp::generate_doc(bool p_use_cache) {
DEV_ASSERT(first_attempt == (doc == nullptr));
if (!doc) {
- // Classes registered after this point should not have documentation generated.
- ClassDB::get_class_list(&classes_whitelist);
-
GDREGISTER_CLASS(DocCache);
doc = memnew(DocTools);
}
@@ -2265,22 +2310,6 @@ void EditorHelp::generate_doc(bool p_use_cache) {
} else {
print_verbose("Regenerating editor help cache");
- if (!first_attempt) {
- // Some classes that should not be exposed may have been registered by now. Unexpose them.
- // Arduous, but happens only when regenerating.
- List<StringName> current_classes;
- ClassDB::get_class_list(&current_classes);
- List<StringName>::Element *W = classes_whitelist.front();
- for (const StringName &name : current_classes) {
- if (W && W->get() == name) {
- W = W->next();
- } else {
- ClassDB::classes[name].exposed = false;
- }
- }
- }
- classes_whitelist.clear();
-
// Not doable on threads unfortunately, since it instantiates all sorts of classes to get default values.
doc->generate(true);
@@ -2328,9 +2357,9 @@ void EditorHelp::go_to_help(const String &p_help) {
_help_callback(p_help);
}
-void EditorHelp::go_to_class(const String &p_class, int p_scroll) {
+void EditorHelp::go_to_class(const String &p_class) {
_wait_for_thread();
- _goto_desc(p_class, p_scroll);
+ _goto_desc(p_class);
}
void EditorHelp::update_doc() {
@@ -2461,14 +2490,15 @@ void EditorHelpBit::_go_to_help(String p_what) {
}
void EditorHelpBit::_meta_clicked(String p_select) {
- if (p_select.begins_with("$")) { //enum
-
+ if (p_select.begins_with("$")) { // enum
String select = p_select.substr(1, p_select.length());
String class_name;
- if (select.contains(".")) {
- class_name = select.get_slice(".", 0);
+ int rfind = select.rfind(".");
+ if (rfind != -1) {
+ class_name = select.substr(0, rfind);
+ select = select.substr(rfind + 1);
} else {
- class_name = "@Global";
+ class_name = "@GlobalScope";
}
_go_to_help("class_enum:" + class_name + ":" + select);
return;
diff --git a/editor/editor_help.h b/editor/editor_help.h
index 01e91b4593..4175ece816 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -178,7 +178,7 @@ class EditorHelp : public VBoxContainer {
void _class_desc_resized(bool p_force_update_theme);
int display_margin = 0;
- Error _goto_desc(const String &p_class, int p_vscr = -1);
+ Error _goto_desc(const String &p_class);
//void _update_history_buttons();
void _update_method_list(const Vector<DocData::MethodDoc> p_methods);
void _update_method_descriptions(const DocData::ClassDoc p_classdoc, const Vector<DocData::MethodDoc> p_methods, const String &p_method_type);
@@ -210,7 +210,7 @@ public:
static String get_cache_full_path();
void go_to_help(const String &p_help);
- void go_to_class(const String &p_class, int p_scroll = 0);
+ void go_to_class(const String &p_class);
void update_doc();
Vector<Pair<String, int>> get_sections();
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 99803fd82d..167a5a3dba 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/filesystem_dock.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/inspector_dock.h"
#include "main/main.h"
#include "scene/gui/box_container.h"
@@ -317,35 +318,35 @@ void EditorInterface::save_scene_as(const String &p_scene, bool p_with_preview)
// Scene playback.
void EditorInterface::play_main_scene() {
- EditorNode::get_singleton()->run_play();
+ EditorRunBar::get_singleton()->play_main_scene();
}
void EditorInterface::play_current_scene() {
- EditorNode::get_singleton()->run_play_current();
+ EditorRunBar::get_singleton()->play_current_scene();
}
void EditorInterface::play_custom_scene(const String &scene_path) {
- EditorNode::get_singleton()->run_play_custom(scene_path);
+ EditorRunBar::get_singleton()->play_custom_scene(scene_path);
}
void EditorInterface::stop_playing_scene() {
- EditorNode::get_singleton()->run_stop();
+ EditorRunBar::get_singleton()->stop_playing();
}
bool EditorInterface::is_playing_scene() const {
- return EditorNode::get_singleton()->is_run_playing();
+ return EditorRunBar::get_singleton()->is_playing();
}
String EditorInterface::get_playing_scene() const {
- return EditorNode::get_singleton()->get_run_playing_scene();
+ return EditorRunBar::get_singleton()->get_playing_scene();
}
void EditorInterface::set_movie_maker_enabled(bool p_enabled) {
- EditorNode::get_singleton()->set_movie_maker_enabled(p_enabled);
+ EditorRunBar::get_singleton()->set_movie_maker_enabled(p_enabled);
}
bool EditorInterface::is_movie_maker_enabled() const {
- return EditorNode::get_singleton()->is_movie_maker_enabled();
+ return EditorRunBar::get_singleton()->is_movie_maker_enabled();
}
// Base.
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d6944b1ed0..3681f61cd8 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -53,6 +53,8 @@
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/split_container.h"
#include "scene/gui/tab_bar.h"
#include "scene/gui/tab_container.h"
@@ -99,6 +101,7 @@
#include "editor/fbx_importer_manager.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_title_bar.h"
#include "editor/gui/editor_toaster.h"
#include "editor/history_dock.h"
@@ -805,20 +808,6 @@ void EditorNode::_notification(int p_what) {
_build_icon_type_cache();
- if (write_movie_button->is_pressed()) {
- launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadMovieMode"), SNAME("EditorStyles")));
- write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonPressed"), SNAME("EditorStyles")));
- } else {
- launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles")));
- write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles")));
- }
-
- play_button->set_icon(gui_base->get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons")));
- play_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayScene"), SNAME("EditorIcons")));
- play_custom_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayCustom"), SNAME("EditorIcons")));
- pause_button->set_icon(gui_base->get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")));
- stop_button->set_icon(gui_base->get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
-
prev_scene->set_icon(gui_base->get_theme_icon(SNAME("PrevScene"), SNAME("EditorIcons")));
distraction_free->set_icon(gui_base->get_theme_icon(SNAME("DistractionFree"), SNAME("EditorIcons")));
scene_tab_add->set_icon(gui_base->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
@@ -1197,7 +1186,7 @@ void EditorNode::_vp_resized() {
}
void EditorNode::_titlebar_resized() {
- DisplayServer::get_singleton()->window_set_window_buttons_offset(Vector2i(menu_hb->get_global_position().y + menu_hb->get_size().y / 2, menu_hb->get_global_position().y + menu_hb->get_size().y / 2), DisplayServer::MAIN_WINDOW_ID);
+ DisplayServer::get_singleton()->window_set_window_buttons_offset(Vector2i(title_bar->get_global_position().y + title_bar->get_size().y / 2, title_bar->get_global_position().y + title_bar->get_size().y / 2), DisplayServer::MAIN_WINDOW_ID);
const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);
if (left_menu_spacer) {
int w = (gui_base->is_layout_rtl()) ? margin.y : margin.x;
@@ -1207,8 +1196,8 @@ void EditorNode::_titlebar_resized() {
int w = (gui_base->is_layout_rtl()) ? margin.x : margin.y;
right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
}
- if (menu_hb) {
- menu_hb->set_custom_minimum_size(Size2(0, margin.z - menu_hb->get_global_position().y));
+ if (title_bar) {
+ title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));
}
}
@@ -1817,7 +1806,7 @@ void EditorNode::_save_scene(String p_file, int idx) {
editor_data.apply_changes_in_editors();
List<Ref<AnimatedValuesBackup>> anim_backups;
_reset_animation_players(scene, &anim_backups);
- _save_default_environment();
+ save_default_environment();
_set_scene_metadata(p_file, idx);
@@ -1879,7 +1868,7 @@ void EditorNode::_save_scene(String p_file, int idx) {
}
void EditorNode::save_all_scenes() {
- _menu_option_confirm(RUN_STOP, true);
+ project_run_bar->stop_playing();
_save_all_scenes();
}
@@ -1893,11 +1882,33 @@ void EditorNode::save_scene_list(Vector<String> p_scene_filenames) {
}
}
+void EditorNode::save_before_run() {
+ current_menu_option = FILE_SAVE_AND_RUN;
+ _menu_option_confirm(FILE_SAVE_AS_SCENE, true);
+ file->set_title(TTR("Save scene before running..."));
+}
+
+void EditorNode::try_autosave() {
+ if (!bool(EDITOR_GET("run/auto_save/save_before_running"))) {
+ return;
+ }
+
+ if (unsaved_cache) {
+ Node *scene = editor_data.get_edited_scene_root();
+
+ if (scene && !scene->get_scene_file_path().is_empty()) { // Only autosave if there is a scene and if it has a path.
+ _save_scene_with_preview(scene->get_scene_file_path());
+ }
+ }
+ _menu_option(FILE_SAVE_ALL_SCENES);
+ editor_data.save_editor_external_data();
+}
+
void EditorNode::restart_editor() {
exiting = true;
- if (editor_run.get_status() != EditorRun::STATUS_STOP) {
- editor_run.stop();
+ if (project_run_bar->is_playing()) {
+ project_run_bar->stop_playing();
}
String to_reopen;
@@ -1945,7 +1956,7 @@ void EditorNode::_save_all_scenes() {
if (!all_saved) {
show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes"));
}
- _save_default_environment();
+ save_default_environment();
}
void EditorNode::_mark_unsaved_scenes() {
@@ -1985,11 +1996,7 @@ void EditorNode::_dialog_action(String p_file) {
ProjectSettings::get_singleton()->save();
// TODO: Would be nice to show the project manager opened with the highlighted field.
- if ((bool)pick_main_scene->get_meta("from_native", false)) {
- run_native->resume_run_native();
- } else {
- _run(false, ""); // Automatically run the project.
- }
+ project_run_bar->play_main_scene((bool)pick_main_scene->get_meta("from_native", false));
} break;
case FILE_CLOSE:
case SCENE_TAB_CLOSE:
@@ -2010,7 +2017,7 @@ void EditorNode::_dialog_action(String p_file) {
return;
}
- _save_default_environment();
+ save_default_environment();
_save_scene_with_preview(p_file, scene_idx);
_add_to_recent_scenes(p_file);
save_layout();
@@ -2024,9 +2031,9 @@ void EditorNode::_dialog_action(String p_file) {
case FILE_SAVE_AND_RUN: {
if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
- _save_default_environment();
+ save_default_environment();
_save_scene_with_preview(p_file);
- _run(false, p_file);
+ project_run_bar->play_custom_scene(p_file);
}
} break;
@@ -2035,13 +2042,9 @@ void EditorNode::_dialog_action(String p_file) {
ProjectSettings::get_singleton()->save();
if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
- _save_default_environment();
+ save_default_environment();
_save_scene_with_preview(p_file);
- if ((bool)pick_main_scene->get_meta("from_native", false)) {
- run_native->resume_run_native();
- } else {
- _run(false, p_file);
- }
+ project_run_bar->play_main_scene((bool)pick_main_scene->get_meta("from_native", false));
}
} break;
@@ -2253,7 +2256,7 @@ void EditorNode::push_item(Object *p_object, const String &p_property, bool p_in
_edit_current();
}
-void EditorNode::_save_default_environment() {
+void EditorNode::save_default_environment() {
Ref<Environment> fallback = get_tree()->get_root()->get_world_3d()->get_fallback_environment();
if (fallback.is_valid() && fallback->get_path().is_resource_file()) {
@@ -2499,157 +2502,6 @@ void EditorNode::_edit_current(bool p_skip_foreign) {
InspectorDock::get_singleton()->update(current_obj);
}
-void EditorNode::_write_movie_toggled(bool p_enabled) {
- if (p_enabled) {
- launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadMovieMode"), SNAME("EditorStyles")));
- write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonPressed"), SNAME("EditorStyles")));
- } else {
- launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles")));
- write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles")));
- }
-}
-
-void EditorNode::_run(bool p_current, const String &p_custom) {
- if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
- play_button->set_pressed(!_playing_edited);
- play_scene_button->set_pressed(_playing_edited);
- return;
- }
-
- play_button->set_pressed(false);
- play_button->set_icon(gui_base->get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons")));
- play_scene_button->set_pressed(false);
- play_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayScene"), SNAME("EditorIcons")));
- play_custom_scene_button->set_pressed(false);
- play_custom_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayCustom"), SNAME("EditorIcons")));
-
- String write_movie_file;
- if (write_movie_button->is_pressed()) {
- if (p_current && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->has_meta("movie_file")) {
- // If the scene file has a movie_file metadata set, use this as file. Quick workaround if you want to have multiple scenes that write to multiple movies.
- write_movie_file = get_tree()->get_edited_scene_root()->get_meta("movie_file");
- } else {
- write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
- }
- if (write_movie_file == String()) {
- show_accept(TTR("Movie Maker mode is enabled, but no movie file path has been specified.\nA default movie file path can be specified in the project settings under the Editor > Movie Writer category.\nAlternatively, for running single scenes, a `movie_file` string metadata can be added to the root node,\nspecifying the path to a movie file that will be used when recording that scene."), TTR("OK"));
- return;
- }
- }
-
- String run_filename;
-
- if ((p_current && p_custom.is_empty()) || (editor_data.get_edited_scene_root() && !p_custom.is_empty() && p_custom == editor_data.get_edited_scene_root()->get_scene_file_path())) {
- Node *scene = editor_data.get_edited_scene_root();
-
- if (!scene) {
- show_accept(TTR("There is no defined scene to run."), TTR("OK"));
- return;
- }
-
- if (scene->get_scene_file_path().is_empty()) {
- current_menu_option = FILE_SAVE_AND_RUN;
- _menu_option_confirm(FILE_SAVE_AS_SCENE, true);
- file->set_title(TTR("Save scene before running..."));
- return;
- }
-
- run_filename = scene->get_scene_file_path();
- } else if (!p_custom.is_empty()) {
- run_filename = p_custom;
- }
-
- if (run_filename.is_empty()) {
- // Evidently, run the scene.
- if (!ensure_main_scene(false)) {
- return;
- }
- run_filename = GLOBAL_DEF_BASIC("application/run/main_scene", "");
- }
-
- if (bool(EDITOR_GET("run/auto_save/save_before_running"))) {
- if (unsaved_cache) {
- Node *scene = editor_data.get_edited_scene_root();
-
- if (scene && !scene->get_scene_file_path().is_empty()) { // Only autosave if there is a scene and if it has a path.
- _save_scene_with_preview(scene->get_scene_file_path());
- }
- }
- _menu_option(FILE_SAVE_ALL_SCENES);
- editor_data.save_editor_external_data();
- }
-
- if (!call_build()) {
- return;
- }
-
- if (bool(EDITOR_GET("run/output/always_clear_output_on_play"))) {
- log->clear();
- }
-
- if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) {
- make_bottom_panel_item_visible(log);
- }
-
- EditorDebuggerNode::get_singleton()->start();
- Error error = editor_run.run(run_filename, write_movie_file);
- if (error != OK) {
- EditorDebuggerNode::get_singleton()->stop();
- show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
- return;
- }
-
- emit_signal(SNAME("play_pressed"));
- if (p_current) {
- run_current_filename = run_filename;
- play_scene_button->set_pressed(true);
- play_scene_button->set_icon(gui_base->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
- play_scene_button->set_tooltip_text(TTR("Reload the played scene."));
- } else if (!p_custom.is_empty()) {
- run_custom_filename = p_custom;
- play_custom_scene_button->set_pressed(true);
- play_custom_scene_button->set_icon(gui_base->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
- play_custom_scene_button->set_tooltip_text(TTR("Reload the played scene."));
- } else {
- play_button->set_pressed(true);
- play_button->set_icon(gui_base->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
- play_button->set_tooltip_text(TTR("Reload the played scene."));
- }
- stop_button->set_disabled(false);
-
- _playing_edited = p_current;
-}
-
-void EditorNode::_run_native(const Ref<EditorExportPreset> &p_preset) {
- bool autosave = EDITOR_GET("run/auto_save/save_before_running");
- if (autosave) {
- _menu_option_confirm(FILE_SAVE_ALL_SCENES, false);
- }
- if (run_native->is_deploy_debug_remote_enabled()) {
- _menu_option_confirm(RUN_STOP, true);
-
- if (!call_build()) {
- return; // Build failed.
- }
-
- EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
- emit_signal(SNAME("play_pressed"));
- editor_run.run_native_notify();
- }
-}
-
-void EditorNode::_reset_play_buttons() {
- play_button->set_pressed(false);
- play_button->set_icon(gui_base->get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons")));
- play_button->set_tooltip_text(TTR("Play the project."));
- play_scene_button->set_pressed(false);
- play_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayScene"), SNAME("EditorIcons")));
- play_scene_button->set_tooltip_text(TTR("Play the edited scene."));
- play_custom_scene_button->set_pressed(false);
- play_custom_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayCustom"), SNAME("EditorIcons")));
- play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
-}
-
void EditorNode::_android_build_source_selected(const String &p_file) {
export_template_manager->install_android_template_from_file(p_file);
}
@@ -2816,6 +2668,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
_save_all_scenes();
} break;
+ case FILE_RUN_SCENE: {
+ project_run_bar->play_current_scene();
+ } break;
+
case FILE_EXPORT_PROJECT: {
project_export->popup_export();
} break;
@@ -2920,45 +2776,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
scene_tabs->set_current_tab(cur_idx);
} break;
- case RUN_PLAY: {
- run_play();
-
- } break;
- case RUN_PLAY_CUSTOM_SCENE: {
- if (run_custom_filename.is_empty() || editor_run.get_status() == EditorRun::STATUS_STOP) {
- _menu_option_confirm(RUN_STOP, true);
- quick_run->popup_dialog("PackedScene", true);
- quick_run->set_title(TTR("Quick Run Scene..."));
- play_custom_scene_button->set_pressed(false);
- } else {
- String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
- run_play_custom(last_custom_scene);
- }
-
- } break;
- case RUN_STOP: {
- if (editor_run.get_status() == EditorRun::STATUS_STOP) {
- break;
- }
-
- editor_run.stop();
- run_custom_filename.clear();
- run_current_filename.clear();
- stop_button->set_disabled(true);
- _reset_play_buttons();
-
- if (bool(EDITOR_GET("run/output/always_close_output_on_stop"))) {
- for (int i = 0; i < bottom_panel_items.size(); i++) {
- if (bottom_panel_items[i].control == log) {
- _bottom_panel_switch(false, i);
- break;
- }
- }
- }
- EditorDebuggerNode::get_singleton()->stop();
- emit_signal(SNAME("stop_pressed"));
-
- } break;
case FILE_SHOW_IN_FILESYSTEM: {
String path = editor_data.get_scene_path(editor_data.get_edited_scene());
@@ -2967,15 +2784,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
}
} break;
- case RUN_PLAY_SCENE: {
- if (run_current_filename.is_empty() || editor_run.get_status() == EditorRun::STATUS_STOP) {
- run_play_current();
- } else {
- String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
- run_play_custom(last_current_scene);
- }
-
- } break;
case RUN_SETTINGS: {
project_settings_editor->popup_project_settings();
} break;
@@ -3299,12 +3107,12 @@ void EditorNode::_discard_changes(const String &p_str) {
_proceed_closing_scene_tabs();
} break;
case FILE_QUIT: {
- _menu_option_confirm(RUN_STOP, true);
+ project_run_bar->stop_playing();
_exit_editor(EXIT_SUCCESS);
} break;
case RUN_PROJECT_MANAGER: {
- _menu_option_confirm(RUN_STOP, true);
+ project_run_bar->stop_playing();
_exit_editor(EXIT_SUCCESS);
String exec = OS::get_singleton()->get_executable_path();
@@ -3587,14 +3395,6 @@ bool EditorNode::is_addon_plugin_enabled(const String &p_addon) const {
return addon_name_to_plugin.has("res://addons/" + p_addon + "/plugin.cfg");
}
-void EditorNode::set_movie_maker_enabled(bool p_enabled) {
- write_movie_button->set_pressed(p_enabled);
-}
-
-bool EditorNode::is_movie_maker_enabled() const {
- return write_movie_button->is_pressed();
-}
-
void EditorNode::_remove_edited_scene(bool p_change_tab) {
int new_index = editor_data.get_edited_scene();
int old_index = new_index;
@@ -4263,14 +4063,31 @@ void EditorNode::_quick_opened() {
}
}
-void EditorNode::_quick_run() {
- _run(false, quick_run->get_selected());
+void EditorNode::_project_run_started() {
+ if (bool(EDITOR_GET("run/output/always_clear_output_on_play"))) {
+ log->clear();
+ }
+
+ if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) {
+ make_bottom_panel_item_visible(log);
+ }
+}
+
+void EditorNode::_project_run_stopped() {
+ if (!bool(EDITOR_GET("run/output/always_close_output_on_stop"))) {
+ return;
+ }
+
+ for (int i = 0; i < bottom_panel_items.size(); i++) {
+ if (bottom_panel_items[i].control == log) {
+ _bottom_panel_switch(false, i);
+ break;
+ }
+ }
}
void EditorNode::notify_all_debug_sessions_exited() {
- _menu_option_confirm(RUN_STOP, false);
- stop_button->set_pressed(false);
- editor_run.stop();
+ project_run_bar->stop_playing();
}
void EditorNode::add_io_error(const String &p_error) {
@@ -4308,13 +4125,12 @@ bool EditorNode::is_scene_in_use(const String &p_path) {
return false;
}
+OS::ProcessID EditorNode::has_child_process(OS::ProcessID p_pid) const {
+ return project_run_bar->has_child_process(p_pid);
+}
+
void EditorNode::stop_child_process(OS::ProcessID p_pid) {
- if (has_child_process(p_pid)) {
- editor_run.stop_child_process(p_pid);
- if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
- _menu_option_confirm(RUN_STOP, false);
- }
- }
+ project_run_bar->stop_child_process(p_pid);
}
Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const {
@@ -5277,45 +5093,6 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
return true;
}
-Error EditorNode::run_play_native(int p_id) {
- return run_native->run_native(p_id);
-}
-
-void EditorNode::run_play() {
- _menu_option_confirm(RUN_STOP, true);
- _run(false);
-}
-
-void EditorNode::run_play_current() {
- _save_default_environment();
- _menu_option_confirm(RUN_STOP, true);
- _run(true);
-}
-
-void EditorNode::run_play_custom(const String &p_custom) {
- bool is_current = !run_current_filename.is_empty();
- _menu_option_confirm(RUN_STOP, true);
- _run(is_current, p_custom);
-}
-
-void EditorNode::run_stop() {
- _menu_option_confirm(RUN_STOP, false);
-}
-
-bool EditorNode::is_run_playing() const {
- EditorRun::Status status = editor_run.get_status();
- return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
-}
-
-String EditorNode::get_run_playing_scene() const {
- String run_filename = editor_run.get_running_scene();
- if (run_filename.is_empty() && is_run_playing()) {
- run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
- }
-
- return run_filename;
-}
-
void EditorNode::_immediate_dialog_confirmed() {
immediate_dialog_confirmed = true;
}
@@ -5536,7 +5313,7 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {
if (scene_tabs->get_hovered_tab() >= 0) {
scene_tabs_context_menu->add_separator();
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), FILE_SHOW_IN_FILESYSTEM);
- scene_tabs_context_menu->add_item(TTR("Play This Scene"), RUN_PLAY_SCENE);
+ scene_tabs_context_menu->add_item(TTR("Play This Scene"), FILE_RUN_SCENE);
scene_tabs_context_menu->add_separator();
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene"), FILE_CLOSE);
@@ -6549,8 +6326,6 @@ void EditorNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_gui_base"), &EditorNode::get_gui_base);
- ADD_SIGNAL(MethodInfo("play_pressed"));
- ADD_SIGNAL(MethodInfo("stop_pressed"));
ADD_SIGNAL(MethodInfo("request_help_search"));
ADD_SIGNAL(MethodInfo("script_add_function_request", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::STRING, "function"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "args")));
ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "obj")));
@@ -6934,8 +6709,8 @@ EditorNode::EditorNode() {
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 8);
main_vbox->add_theme_constant_override("separation", 8 * EDSCALE);
- menu_hb = memnew(EditorTitleBar);
- main_vbox->add_child(menu_hb);
+ title_bar = memnew(EditorTitleBar);
+ main_vbox->add_child(title_bar);
left_l_hsplit = memnew(HSplitContainer);
main_vbox->add_child(left_l_hsplit);
@@ -7175,11 +6950,11 @@ EditorNode::EditorNode() {
// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).
left_menu_spacer = memnew(Control);
left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
- menu_hb->add_child(left_menu_spacer);
+ title_bar->add_child(left_menu_spacer);
}
main_menu = memnew(MenuBar);
- menu_hb->add_child(main_menu);
+ title_bar->add_child(main_menu);
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
main_menu->set_flat(true);
@@ -7355,7 +7130,7 @@ EditorNode::EditorNode() {
HBoxContainer *left_spacer = memnew(HBoxContainer);
left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- menu_hb->add_child(left_spacer);
+ title_bar->add_child(left_spacer);
if (can_expand && global_menu) {
project_title = memnew(Label);
@@ -7370,7 +7145,7 @@ EditorNode::EditorNode() {
}
main_editor_button_hb = memnew(HBoxContainer);
- menu_hb->add_child(main_editor_button_hb);
+ title_bar->add_child(main_editor_button_hb);
// Options are added and handled by DebuggerEditorPlugin.
debug_menu = memnew(PopupMenu);
@@ -7455,104 +7230,15 @@ EditorNode::EditorNode() {
Control *right_spacer = memnew(Control);
right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- menu_hb->add_child(right_spacer);
-
- launch_pad = memnew(PanelContainer);
- launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles")));
- menu_hb->add_child(launch_pad);
-
- HBoxContainer *launch_pad_hb = memnew(HBoxContainer);
- launch_pad->add_child(launch_pad_hb);
-
- play_button = memnew(Button);
- play_button->set_flat(true);
- launch_pad_hb->add_child(play_button);
- play_button->set_toggle_mode(true);
- play_button->set_focus_mode(Control::FOCUS_NONE);
- play_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(RUN_PLAY));
- play_button->set_tooltip_text(TTR("Run the project's default scene."));
-
- ED_SHORTCUT_AND_COMMAND("editor/run_project", TTR("Run Project"), Key::F5);
- ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
- play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
-
- pause_button = memnew(Button);
- pause_button->set_flat(true);
- pause_button->set_toggle_mode(true);
- pause_button->set_icon(gui_base->get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")));
- pause_button->set_focus_mode(Control::FOCUS_NONE);
- pause_button->set_tooltip_text(TTR("Pause the running project's execution for debugging."));
- pause_button->set_disabled(true);
- launch_pad_hb->add_child(pause_button);
-
- ED_SHORTCUT("editor/pause_running_project", TTR("Pause Running Project"), Key::F7);
- ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
- pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
-
- stop_button = memnew(Button);
- stop_button->set_flat(true);
- launch_pad_hb->add_child(stop_button);
- stop_button->set_focus_mode(Control::FOCUS_NONE);
- stop_button->set_icon(gui_base->get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
- stop_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(RUN_STOP));
- stop_button->set_tooltip_text(TTR("Stop the currently running project."));
- stop_button->set_disabled(true);
-
- ED_SHORTCUT("editor/stop_running_project", TTR("Stop Running Project"), Key::F8);
- ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
- stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
-
- run_native = memnew(EditorRunNative);
- launch_pad_hb->add_child(run_native);
- run_native->connect("native_run", callable_mp(this, &EditorNode::_run_native));
-
- play_scene_button = memnew(Button);
- play_scene_button->set_flat(true);
- launch_pad_hb->add_child(play_scene_button);
- play_scene_button->set_toggle_mode(true);
- play_scene_button->set_focus_mode(Control::FOCUS_NONE);
- play_scene_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(RUN_PLAY_SCENE));
- play_scene_button->set_tooltip_text(TTR("Run the currently edited scene."));
-
- ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTR("Run Current Scene"), Key::F6);
- ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
- play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
-
- play_custom_scene_button = memnew(Button);
- play_custom_scene_button->set_flat(true);
- launch_pad_hb->add_child(play_custom_scene_button);
- play_custom_scene_button->set_toggle_mode(true);
- play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
- play_custom_scene_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(RUN_PLAY_CUSTOM_SCENE));
- play_custom_scene_button->set_tooltip_text(TTR("Run a specific scene."));
-
- _reset_play_buttons();
-
- ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTR("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
- ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
- play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
-
- write_movie_panel = memnew(PanelContainer);
- write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles")));
- launch_pad_hb->add_child(write_movie_panel);
-
- write_movie_button = memnew(Button);
- write_movie_button->set_flat(true);
- write_movie_button->set_toggle_mode(true);
- write_movie_panel->add_child(write_movie_button);
- write_movie_button->set_pressed(false);
- write_movie_button->set_icon(gui_base->get_theme_icon(SNAME("MainMovieWrite"), SNAME("EditorIcons")));
- write_movie_button->set_focus_mode(Control::FOCUS_NONE);
- write_movie_button->connect("toggled", callable_mp(this, &EditorNode::_write_movie_toggled));
- write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
-
- // This button behaves differently, so color it as such.
- write_movie_button->add_theme_color_override("icon_normal_color", Color(1, 1, 1, 0.7));
- write_movie_button->add_theme_color_override("icon_pressed_color", Color(0, 0, 0, 0.84));
- write_movie_button->add_theme_color_override("icon_hover_color", Color(1, 1, 1, 0.9));
+ title_bar->add_child(right_spacer);
+
+ project_run_bar = memnew(EditorRunBar);
+ title_bar->add_child(project_run_bar);
+ project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started));
+ project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped));
HBoxContainer *right_menu_hb = memnew(HBoxContainer);
- menu_hb->add_child(right_menu_hb);
+ title_bar->add_child(right_menu_hb);
renderer = memnew(OptionButton);
renderer->set_visible(true);
@@ -7570,7 +7256,7 @@ EditorNode::EditorNode() {
// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).
right_menu_spacer = memnew(Control);
right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
- menu_hb->add_child(right_menu_spacer);
+ title_bar->add_child(right_menu_spacer);
}
String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method");
@@ -8007,10 +7693,6 @@ EditorNode::EditorNode() {
gui_base->add_child(quick_open);
quick_open->connect("quick_open", callable_mp(this, &EditorNode::_quick_opened));
- quick_run = memnew(EditorQuickOpen);
- gui_base->add_child(quick_run);
- quick_run->connect("quick_open", callable_mp(this, &EditorNode::_quick_run));
-
_update_recent_scenes();
set_process_shortcut_input(true);
@@ -8082,14 +7764,14 @@ EditorNode::EditorNode() {
screenshot_timer->set_owner(get_owner());
// Adjust spacers to center 2D / 3D / Script buttons.
- int max_w = MAX(launch_pad->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
+ int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu->get_minimum_size().x), 0));
- right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - launch_pad->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
+ right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
// Extend menu bar to window title.
if (can_expand) {
DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, true, DisplayServer::MAIN_WINDOW_ID);
- menu_hb->set_can_move_window(true);
+ title_bar->set_can_move_window(true);
}
String exec = OS::get_singleton()->get_executable_path();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 831e2989f5..0003fee301 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -35,8 +35,6 @@
#include "editor/editor_data.h"
#include "editor/editor_folding.h"
#include "editor/editor_plugin.h"
-#include "editor/editor_run.h"
-#include "editor/export/editor_export.h"
typedef void (*EditorNodeInitCallback)();
typedef void (*EditorPluginInitializeCallback)();
@@ -59,6 +57,8 @@ class Node2D;
class OptionButton;
class Panel;
class PanelContainer;
+class PopupPanel;
+class RichTextLabel;
class SubViewport;
class TabBar;
class TabContainer;
@@ -92,6 +92,7 @@ class EditorQuickOpen;
class EditorPropertyResource;
class EditorResourcePreview;
class EditorResourceConversionPlugin;
+class EditorRunBar;
class EditorRunNative;
class EditorSelectionHistory;
class EditorSettingsDialog;
@@ -162,6 +163,7 @@ private:
FILE_SAVE_ALL_SCENES,
FILE_SAVE_AND_RUN,
FILE_SAVE_AND_RUN_MAIN_SCENE,
+ FILE_RUN_SCENE,
FILE_SHOW_IN_FILESYSTEM,
FILE_EXPORT_PROJECT,
FILE_EXPORT_MESH_LIBRARY,
@@ -188,11 +190,7 @@ private:
TOOLS_CUSTOM,
RESOURCE_SAVE,
RESOURCE_SAVE_AS,
- RUN_PLAY,
- RUN_STOP,
- RUN_PLAY_SCENE,
- RUN_PLAY_CUSTOM_SCENE,
RUN_SETTINGS,
RUN_USER_DATA_FOLDER,
RELOAD_CURRENT_PROJECT,
@@ -265,7 +263,6 @@ private:
EditorData editor_data;
EditorFolding editor_folding;
- EditorRun editor_run;
EditorSelectionHistory editor_history;
EditorCommandPalette *command_palette = nullptr;
@@ -277,9 +274,7 @@ private:
EditorPluginList *editor_plugins_force_over = nullptr;
EditorPluginList *editor_plugins_over = nullptr;
EditorQuickOpen *quick_open = nullptr;
- EditorQuickOpen *quick_run = nullptr;
EditorResourcePreview *resource_preview = nullptr;
- EditorRunNative *run_native = nullptr;
EditorSelection *editor_selection = nullptr;
EditorSettingsDialog *editor_settings_dialog = nullptr;
HistoryDock *history_dock = nullptr;
@@ -342,7 +337,8 @@ private:
Label *project_title = nullptr;
Control *left_menu_spacer = nullptr;
Control *right_menu_spacer = nullptr;
- EditorTitleBar *menu_hb = nullptr;
+ EditorTitleBar *title_bar = nullptr;
+ EditorRunBar *project_run_bar = nullptr;
VBoxContainer *main_screen_vbox = nullptr;
MenuBar *main_menu = nullptr;
PopupMenu *file_menu = nullptr;
@@ -357,15 +353,6 @@ private:
Button *search_button = nullptr;
TextureProgressBar *audio_vu = nullptr;
- PanelContainer *launch_pad = nullptr;
- Button *play_button = nullptr;
- Button *pause_button = nullptr;
- Button *stop_button = nullptr;
- Button *play_scene_button = nullptr;
- Button *play_custom_scene_button = nullptr;
- PanelContainer *write_movie_panel = nullptr;
- Button *write_movie_button = nullptr;
-
Timer *screenshot_timer = nullptr;
PluginConfigDialog *plugin_config_dialog = nullptr;
@@ -469,7 +456,6 @@ private:
bool scene_distraction_free = false;
bool script_distraction_free = false;
- bool _playing_edited = false;
bool changing_scene = false;
bool cmdline_export_mode = false;
bool convert_old = false;
@@ -496,9 +482,6 @@ private:
String external_file;
String open_navigate;
- String run_custom_filename;
- String run_current_filename;
-
DynamicFontImportSettings *fontdata_import_settings = nullptr;
SceneImportSettings *scene_import_settings = nullptr;
AudioStreamImportSettings *audio_stream_import_settings = nullptr;
@@ -600,14 +583,10 @@ private:
void _instantiate_request(const Vector<String> &p_files);
void _quick_opened();
- void _quick_run();
void _open_command_palette();
- void _write_movie_toggled(bool p_enabled);
-
- void _run(bool p_current = false, const String &p_custom = "");
- void _run_native(const Ref<EditorExportPreset> &p_preset);
- void _reset_play_buttons();
+ void _project_run_started();
+ void _project_run_stopped();
void _add_to_recent_scenes(const String &p_scene);
void _update_recent_scenes();
@@ -687,7 +666,6 @@ private:
void _inherit_imported(const String &p_action);
void _open_imported();
- void _save_default_environment();
void _update_update_spinner();
void _resources_changed(const Vector<String> &p_resources);
@@ -717,7 +695,6 @@ protected:
friend class FileSystemDock;
static void _bind_methods();
-
void _notification(int p_what);
int get_current_tab();
@@ -738,7 +715,7 @@ public:
static EditorData &get_editor_data() { return singleton->editor_data; }
static EditorFolding &get_editor_folding() { return singleton->editor_folding; }
- static EditorTitleBar *get_menu_hb() { return singleton->menu_hb; }
+ static EditorTitleBar *get_title_bar() { return singleton->title_bar; }
static VSplitContainer *get_top_split() { return singleton->top_split; }
static String adjust_scene_name_casing(const String &root_name);
@@ -789,9 +766,6 @@ public:
void set_addon_plugin_enabled(const String &p_addon, bool p_enabled, bool p_config_changed = false);
bool is_addon_plugin_enabled(const String &p_addon) const;
- void set_movie_maker_enabled(bool p_enabled);
- bool is_movie_maker_enabled() const;
-
void edit_node(Node *p_node);
void edit_resource(const Ref<Resource> &p_resource);
@@ -874,7 +848,7 @@ public:
void notify_all_debug_sessions_exited();
- OS::ProcessID has_child_process(OS::ProcessID p_pid) const { return editor_run.has_child_process(p_pid); }
+ OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
void stop_child_process(OS::ProcessID p_pid);
Ref<Theme> get_editor_theme() const { return theme; }
@@ -908,6 +882,7 @@ public:
bool is_scene_in_use(const String &p_path);
void save_layout();
+ void save_default_environment();
void open_export_template_manager();
@@ -918,8 +893,6 @@ public:
bool is_exiting() const { return exiting; }
- Button *get_pause_button() { return pause_button; }
-
Button *add_bottom_panel_item(String p_text, Control *p_item);
void make_bottom_panel_item_visible(Control *p_item);
void raise_bottom_panel_item(Control *p_item);
@@ -937,6 +910,8 @@ public:
void save_all_scenes();
void save_scene_list(Vector<String> p_scene_filenames);
+ void save_before_run();
+ void try_autosave();
void restart_editor();
void notify_settings_changed();
@@ -958,14 +933,6 @@ public:
Vector<Ref<EditorResourceConversionPlugin>> find_resource_conversion_plugin(const Ref<Resource> &p_for_resource);
bool ensure_main_scene(bool p_from_native);
-
- Error run_play_native(int p_id);
- void run_play();
- void run_play_current();
- void run_play_custom(const String &p_custom);
- void run_stop();
- bool is_run_playing() const;
- String get_run_playing_scene() const;
};
struct EditorProgress {
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 8ac5e77d35..442524d579 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -94,7 +94,7 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C
switch (p_location) {
case CONTAINER_TOOLBAR: {
- EditorNode::get_menu_hb()->add_child(p_control);
+ EditorNode::get_title_bar()->add_child(p_control);
} break;
case CONTAINER_SPATIAL_EDITOR_MENU: {
@@ -147,7 +147,7 @@ void EditorPlugin::remove_control_from_container(CustomControlContainer p_locati
switch (p_location) {
case CONTAINER_TOOLBAR: {
- EditorNode::get_menu_hb()->remove_child(p_control);
+ EditorNode::get_title_bar()->remove_child(p_control);
} break;
case CONTAINER_SPATIAL_EDITOR_MENU: {
diff --git a/editor/editor_plugin_settings.cpp b/editor/editor_plugin_settings.cpp
index 9bcb25e9c0..1e582992d1 100644
--- a/editor/editor_plugin_settings.cpp
+++ b/editor/editor_plugin_settings.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/config_file.h"
+#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/os/main_loop.h"
#include "editor/editor_node.h"
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index bb0434a1bf..3345f87973 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -203,7 +203,18 @@ void EditorPropertyArray::_property_changed(const String &p_property, Variant p_
index = p_property.get_slice("/", 1).to_int();
}
- Variant array = object->get_array().duplicate();
+ Variant array;
+ const Variant &original_array = object->get_array();
+
+ if (original_array.get_type() == Variant::ARRAY) {
+ // Needed to preserve type of TypedArrays in meta pointer properties.
+ Array temp;
+ temp.assign(original_array.duplicate());
+ array = temp;
+ } else {
+ array = original_array.duplicate();
+ }
+
array.set(index, p_value);
object->set_array(array);
emit_changed(get_edited_property(), array, "", true);
diff --git a/editor/editor_run.h b/editor/editor_run.h
index 68c8742f79..bd6770ae3d 100644
--- a/editor/editor_run.h
+++ b/editor/editor_run.h
@@ -50,6 +50,7 @@ private:
public:
Status get_status() const;
String get_running_scene() const;
+
Error run(const String &p_scene, const String &p_write_movie = "");
void run_native_notify() { status = STATUS_PLAY; }
void stop();
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index 815d4fab9c..beccf0f2ec 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -33,6 +33,7 @@
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "editor/export/editor_export_platform.h"
void EditorRunNative::_notification(int p_what) {
@@ -77,7 +78,7 @@ void EditorRunNative::_notification(int p_what) {
}
}
-Error EditorRunNative::run_native(int p_id) {
+Error EditorRunNative::start_run_native(int p_id) {
if (p_id < 0) {
return OK;
}
@@ -142,7 +143,7 @@ Error EditorRunNative::run_native(int p_id) {
}
void EditorRunNative::resume_run_native() {
- run_native(resume_id);
+ start_run_native(resume_id);
}
void EditorRunNative::_bind_methods() {
@@ -155,7 +156,7 @@ bool EditorRunNative::is_deploy_debug_remote_enabled() const {
EditorRunNative::EditorRunNative() {
remote_debug = memnew(MenuButton);
- remote_debug->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native));
+ remote_debug->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::start_run_native));
remote_debug->set_tooltip_text(TTR("Remote Debug"));
remote_debug->set_disabled(true);
diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h
index 2a5431e54b..f52a455bb2 100644
--- a/editor/editor_run_native.h
+++ b/editor/editor_run_native.h
@@ -52,11 +52,11 @@ protected:
void _notification(int p_what);
public:
- Error run_native(int p_id);
- bool is_deploy_debug_remote_enabled() const;
-
+ Error start_run_native(int p_id);
void resume_run_native();
+ bool is_deploy_debug_remote_enabled() const;
+
EditorRunNative();
};
diff --git a/editor/editor_run_script.cpp b/editor/editor_script.cpp
index a459943656..4e8c5ad8b5 100644
--- a/editor/editor_run_script.cpp
+++ b/editor/editor_script.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* editor_run_script.cpp */
+/* editor_script.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,18 +28,18 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "editor_run_script.h"
+#include "editor_script.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
void EditorScript::add_root_node(Node *p_node) {
- if (!editor) {
+ if (!EditorNode::get_singleton()) {
EditorNode::add_io_error("EditorScript::add_root_node: " + TTR("Write your logic in the _run() method."));
return;
}
- if (editor->get_edited_scene()) {
+ if (EditorNode::get_singleton()->get_edited_scene()) {
EditorNode::add_io_error("EditorScript::add_root_node: " + TTR("There is an edited scene already."));
return;
}
@@ -47,36 +47,29 @@ void EditorScript::add_root_node(Node *p_node) {
//editor->set_edited_scene(p_node);
}
-EditorInterface *EditorScript::get_editor_interface() {
- return EditorInterface::get_singleton();
-}
-
-Node *EditorScript::get_scene() {
- if (!editor) {
+Node *EditorScript::get_scene() const {
+ if (!EditorNode::get_singleton()) {
EditorNode::add_io_error("EditorScript::get_scene: " + TTR("Write your logic in the _run() method."));
return nullptr;
}
- return editor->get_edited_scene();
+ return EditorNode::get_singleton()->get_edited_scene();
+}
+
+EditorInterface *EditorScript::get_editor_interface() const {
+ return EditorInterface::get_singleton();
}
-void EditorScript::_run() {
+void EditorScript::run() {
if (!GDVIRTUAL_CALL(_run)) {
EditorNode::add_io_error(TTR("Couldn't run editor script, did you forget to override the '_run' method?"));
}
}
-void EditorScript::set_editor(EditorNode *p_editor) {
- editor = p_editor;
-}
-
void EditorScript::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_root_node", "node"), &EditorScript::add_root_node);
ClassDB::bind_method(D_METHOD("get_scene"), &EditorScript::get_scene);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorScript::get_editor_interface);
- GDVIRTUAL_BIND(_run);
-}
-EditorScript::EditorScript() {
- editor = nullptr;
+ GDVIRTUAL_BIND(_run);
}
diff --git a/editor/editor_run_script.h b/editor/editor_script.h
index 8284d59110..d7c813261d 100644
--- a/editor/editor_run_script.h
+++ b/editor/editor_script.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* editor_run_script.h */
+/* editor_script.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef EDITOR_RUN_SCRIPT_H
-#define EDITOR_RUN_SCRIPT_H
+#ifndef EDITOR_SCRIPT_H
+#define EDITOR_SCRIPT_H
#include "core/object/gdvirtual.gen.inc"
#include "core/object/ref_counted.h"
@@ -41,20 +41,19 @@ class EditorNode;
class EditorScript : public RefCounted {
GDCLASS(EditorScript, RefCounted);
- EditorNode *editor = nullptr;
-
protected:
static void _bind_methods();
+
GDVIRTUAL0(_run)
public:
void add_root_node(Node *p_node);
- Node *get_scene();
- EditorInterface *get_editor_interface();
- virtual void _run();
+ Node *get_scene() const;
+ EditorInterface *get_editor_interface() const;
+
+ virtual void run();
- void set_editor(EditorNode *p_editor);
- EditorScript();
+ EditorScript() {}
};
-#endif // EDITOR_RUN_SCRIPT_H
+#endif // EDITOR_SCRIPT_H
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 2cdce158b5..9a6302b695 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -454,6 +454,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// Inspector
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1")
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "")
+ EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/inspector/float_drag_speed", 5.0, "0.1,100,0.01")
// Theme
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "interface/theme/preset", "Default", "Default,Breeze Dark,Godot 2,Gray,Light,Solarized (Dark),Solarized (Light),Black (OLED),Custom")
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index a21fb9fdfb..660a9501a2 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -31,14 +31,13 @@
#ifndef EDITOR_SETTINGS_H
#define EDITOR_SETTINGS_H
+#include "core/input/shortcut.h"
#include "core/io/config_file.h"
#include "core/io/resource.h"
#include "core/os/thread_safe.h"
#include "core/templates/rb_set.h"
class EditorPlugin;
-class InputEvent;
-class Shortcut;
class EditorSettings : public Resource {
GDCLASS(EditorSettings, Resource);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 32b6e9822a..1a8a216605 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -1604,6 +1604,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("slider", "HSlider", make_flat_stylebox(dark_color_3, 0, default_margin_size / 2, 0, default_margin_size / 2, corner_width));
theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(contrast_color_1, 0, default_margin_size / 2, 0, default_margin_size / 2, corner_width));
theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(contrast_color_1, 0, default_margin_size / 2, 0, default_margin_size / 2));
+ theme->set_constant("center_grabber", "HSlider", 0);
theme->set_constant("grabber_offset", "HSlider", 0);
// VSlider
@@ -1612,6 +1613,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("slider", "VSlider", make_flat_stylebox(dark_color_3, default_margin_size / 2, 0, default_margin_size / 2, 0, corner_width));
theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(contrast_color_1, default_margin_size / 2, 0, default_margin_size / 2, 0, corner_width));
theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(contrast_color_1, default_margin_size / 2, 0, default_margin_size / 2, 0));
+ theme->set_constant("center_grabber", "VSlider", 0);
theme->set_constant("grabber_offset", "VSlider", 0);
// RichTextLabel
@@ -1868,6 +1870,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("sv_height", "ColorPicker", 256 * EDSCALE);
theme->set_constant("h_width", "ColorPicker", 30 * EDSCALE);
theme->set_constant("label_width", "ColorPicker", 10 * EDSCALE);
+ theme->set_constant("center_slider_grabbers", "ColorPicker", 1);
theme->set_icon("screen_picker", "ColorPicker", theme->get_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
theme->set_icon("shape_circle", "ColorPicker", theme->get_icon(SNAME("PickerShapeCircle"), SNAME("EditorIcons")));
theme->set_icon("shape_rect", "ColorPicker", theme->get_icon(SNAME("PickerShapeRectangle"), SNAME("EditorIcons")));
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 4e17379911..121088f27a 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -42,6 +42,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor_export_plugin.h"
#include "scene/resources/packed_scene.h"
diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp
new file mode 100644
index 0000000000..22a3600452
--- /dev/null
+++ b/editor/gui/editor_run_bar.cpp
@@ -0,0 +1,442 @@
+/**************************************************************************/
+/* editor_run_bar.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 "editor_run_bar.h"
+
+#include "core/config/project_settings.h"
+#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_command_palette.h"
+#include "editor/editor_node.h"
+#include "editor/editor_quick_open.h"
+#include "editor/editor_run_native.h"
+#include "editor/editor_settings.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/panel_container.h"
+
+EditorRunBar *EditorRunBar::singleton = nullptr;
+
+void EditorRunBar::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ _update_play_buttons();
+ pause_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")));
+ stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+
+ if (is_movie_maker_enabled()) {
+ main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), SNAME("EditorStyles")));
+ write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), SNAME("EditorStyles")));
+ } else {
+ main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles")));
+ write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles")));
+ }
+
+ write_movie_button->set_icon(get_theme_icon(SNAME("MainMovieWrite"), SNAME("EditorIcons")));
+ // This button behaves differently, so color it as such.
+ write_movie_button->add_theme_color_override("icon_normal_color", Color(1, 1, 1, 0.7));
+ write_movie_button->add_theme_color_override("icon_pressed_color", Color(0, 0, 0, 0.84));
+ write_movie_button->add_theme_color_override("icon_hover_color", Color(1, 1, 1, 0.9));
+ } break;
+ }
+}
+
+void EditorRunBar::_reset_play_buttons() {
+ play_button->set_pressed(false);
+ play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons")));
+ play_button->set_tooltip_text(TTR("Play the project."));
+
+ play_scene_button->set_pressed(false);
+ play_scene_button->set_icon(get_theme_icon(SNAME("PlayScene"), SNAME("EditorIcons")));
+ play_scene_button->set_tooltip_text(TTR("Play the edited scene."));
+
+ play_custom_scene_button->set_pressed(false);
+ play_custom_scene_button->set_icon(get_theme_icon(SNAME("PlayCustom"), SNAME("EditorIcons")));
+ play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
+}
+
+void EditorRunBar::_update_play_buttons() {
+ _reset_play_buttons();
+ if (!is_playing()) {
+ return;
+ }
+
+ Button *active_button = nullptr;
+ if (current_mode == RUN_CURRENT) {
+ active_button = play_scene_button;
+ } else if (current_mode == RUN_CUSTOM) {
+ active_button = play_custom_scene_button;
+ } else {
+ active_button = play_button;
+ }
+
+ if (active_button) {
+ active_button->set_pressed(true);
+ active_button->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
+ active_button->set_tooltip_text(TTR("Reload the played scene."));
+ }
+}
+
+void EditorRunBar::_write_movie_toggled(bool p_enabled) {
+ if (p_enabled) {
+ add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), SNAME("EditorStyles")));
+ write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), SNAME("EditorStyles")));
+ } else {
+ add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles")));
+ write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles")));
+ }
+}
+
+void EditorRunBar::_quick_run_selected() {
+ play_custom_scene(quick_run->get_selected());
+}
+
+void EditorRunBar::_play_custom_pressed() {
+ if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
+ stop_playing();
+
+ quick_run->popup_dialog("PackedScene", true);
+ quick_run->set_title(TTR("Quick Run Scene..."));
+ play_custom_scene_button->set_pressed(false);
+ } else {
+ // Reload if already running a custom scene.
+ String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
+ play_custom_scene(last_custom_scene);
+ }
+}
+
+void EditorRunBar::_play_current_pressed() {
+ if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
+ play_current_scene();
+ } else {
+ // Reload if already running the current scene.
+ play_current_scene(true);
+ }
+}
+
+void EditorRunBar::_run_scene(const String &p_scene_path) {
+ ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path.");
+
+ if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
+ return;
+ }
+
+ _reset_play_buttons();
+
+ String write_movie_file;
+ if (is_movie_maker_enabled()) {
+ if (current_mode == RUN_CURRENT) {
+ Node *scene_root = nullptr;
+ if (p_scene_path.is_empty()) {
+ scene_root = get_tree()->get_edited_scene_root();
+ } else {
+ int scene_index = EditorNode::get_editor_data().get_edited_scene_from_path(p_scene_path);
+ if (scene_index >= 0) {
+ scene_root = EditorNode::get_editor_data().get_edited_scene_root(scene_index);
+ }
+ }
+
+ if (scene_root && scene_root->has_meta("movie_file")) {
+ // If the scene file has a movie_file metadata set, use this as file.
+ // Quick workaround if you want to have multiple scenes that write to
+ // multiple movies.
+ write_movie_file = scene_root->get_meta("movie_file");
+ }
+ }
+
+ if (write_movie_file.is_empty()) {
+ write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
+ }
+
+ if (write_movie_file.is_empty()) {
+ // TODO: Provide options to directly resolve the issue with a custom dialog.
+ EditorNode::get_singleton()->show_accept(TTR("Movie Maker mode is enabled, but no movie file path has been specified.\nA default movie file path can be specified in the project settings under the Editor > Movie Writer category.\nAlternatively, for running single scenes, a `movie_file` string metadata can be added to the root node,\nspecifying the path to a movie file that will be used when recording that scene."), TTR("OK"));
+ return;
+ }
+ }
+
+ String run_filename;
+ switch (current_mode) {
+ case RUN_CUSTOM: {
+ run_filename = p_scene_path;
+ run_custom_filename = run_filename;
+ } break;
+
+ case RUN_CURRENT: {
+ if (!p_scene_path.is_empty()) {
+ run_filename = p_scene_path;
+ run_current_filename = run_filename;
+ break;
+ }
+
+ Node *scene_root = get_tree()->get_edited_scene_root();
+ if (!scene_root) {
+ EditorNode::get_singleton()->show_accept(TTR("There is no defined scene to run."), TTR("OK"));
+ return;
+ }
+
+ if (scene_root->get_scene_file_path().is_empty()) {
+ EditorNode::get_singleton()->save_before_run();
+ return;
+ }
+
+ run_filename = scene_root->get_scene_file_path();
+ run_current_filename = run_filename;
+ } break;
+
+ default: {
+ if (!EditorNode::get_singleton()->ensure_main_scene(false)) {
+ return;
+ }
+
+ run_filename = GLOBAL_DEF_BASIC("application/run/main_scene", "");
+ } break;
+ }
+
+ EditorNode::get_singleton()->try_autosave();
+ if (!EditorNode::get_singleton()->call_build()) {
+ return;
+ }
+
+ EditorDebuggerNode::get_singleton()->start();
+ Error error = editor_run.run(run_filename, write_movie_file);
+ if (error != OK) {
+ EditorDebuggerNode::get_singleton()->stop();
+ EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
+ return;
+ }
+
+ _update_play_buttons();
+ stop_button->set_disabled(false);
+
+ emit_signal(SNAME("play_pressed"));
+}
+
+void EditorRunBar::_run_native(const Ref<EditorExportPreset> &p_preset) {
+ EditorNode::get_singleton()->try_autosave();
+
+ if (run_native->is_deploy_debug_remote_enabled()) {
+ stop_playing();
+
+ if (!EditorNode::get_singleton()->call_build()) {
+ return; // Build failed.
+ }
+
+ EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
+ emit_signal(SNAME("play_pressed"));
+ editor_run.run_native_notify();
+ }
+}
+
+void EditorRunBar::play_main_scene(bool p_from_native) {
+ if (p_from_native) {
+ run_native->resume_run_native();
+ } else {
+ stop_playing();
+
+ current_mode = RunMode::RUN_MAIN;
+ _run_scene();
+ }
+}
+
+void EditorRunBar::play_current_scene(bool p_reload) {
+ EditorNode::get_singleton()->save_default_environment();
+ stop_playing();
+
+ current_mode = RunMode::RUN_CURRENT;
+ if (p_reload) {
+ String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
+ _run_scene(last_current_scene);
+ } else {
+ _run_scene();
+ }
+}
+
+void EditorRunBar::play_custom_scene(const String &p_custom) {
+ stop_playing();
+
+ current_mode = RunMode::RUN_CUSTOM;
+ _run_scene(p_custom);
+}
+
+void EditorRunBar::stop_playing() {
+ if (editor_run.get_status() == EditorRun::STATUS_STOP) {
+ return;
+ }
+
+ current_mode = RunMode::STOPPED;
+ editor_run.stop();
+ EditorDebuggerNode::get_singleton()->stop();
+
+ run_custom_filename.clear();
+ run_current_filename.clear();
+ stop_button->set_pressed(false);
+ stop_button->set_disabled(true);
+ _reset_play_buttons();
+
+ emit_signal(SNAME("stop_pressed"));
+}
+
+bool EditorRunBar::is_playing() const {
+ EditorRun::Status status = editor_run.get_status();
+ return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
+}
+
+String EditorRunBar::get_playing_scene() const {
+ String run_filename = editor_run.get_running_scene();
+ if (run_filename.is_empty() && is_playing()) {
+ run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
+ }
+
+ return run_filename;
+}
+
+Error EditorRunBar::start_native_device(int p_device_id) {
+ return run_native->start_run_native(p_device_id);
+}
+
+OS::ProcessID EditorRunBar::has_child_process(OS::ProcessID p_pid) const {
+ return editor_run.has_child_process(p_pid);
+}
+
+void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
+ if (!has_child_process(p_pid)) {
+ return;
+ }
+
+ editor_run.stop_child_process(p_pid);
+ if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
+ stop_playing();
+ }
+}
+
+void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
+ write_movie_button->set_pressed(p_enabled);
+}
+
+bool EditorRunBar::is_movie_maker_enabled() const {
+ return write_movie_button->is_pressed();
+}
+
+void EditorRunBar::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("play_pressed"));
+ ADD_SIGNAL(MethodInfo("stop_pressed"));
+}
+
+EditorRunBar::EditorRunBar() {
+ singleton = this;
+
+ main_panel = memnew(PanelContainer);
+ add_child(main_panel);
+
+ HBoxContainer *main_hbox = memnew(HBoxContainer);
+ main_panel->add_child(main_hbox);
+
+ play_button = memnew(Button);
+ main_hbox->add_child(play_button);
+ play_button->set_flat(true);
+ play_button->set_toggle_mode(true);
+ play_button->set_focus_mode(Control::FOCUS_NONE);
+ play_button->set_tooltip_text(TTR("Run the project's default scene."));
+ play_button->connect("pressed", callable_mp(this, &EditorRunBar::play_main_scene).bind(false));
+
+ ED_SHORTCUT_AND_COMMAND("editor/run_project", TTR("Run Project"), Key::F5);
+ ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
+ play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
+
+ pause_button = memnew(Button);
+ main_hbox->add_child(pause_button);
+ pause_button->set_flat(true);
+ pause_button->set_toggle_mode(true);
+ pause_button->set_focus_mode(Control::FOCUS_NONE);
+ pause_button->set_tooltip_text(TTR("Pause the running project's execution for debugging."));
+ pause_button->set_disabled(true);
+
+ ED_SHORTCUT("editor/pause_running_project", TTR("Pause Running Project"), Key::F7);
+ ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
+ pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
+
+ stop_button = memnew(Button);
+ main_hbox->add_child(stop_button);
+ stop_button->set_flat(true);
+ stop_button->set_focus_mode(Control::FOCUS_NONE);
+ stop_button->set_tooltip_text(TTR("Stop the currently running project."));
+ stop_button->set_disabled(true);
+ stop_button->connect("pressed", callable_mp(this, &EditorRunBar::stop_playing));
+
+ ED_SHORTCUT("editor/stop_running_project", TTR("Stop Running Project"), Key::F8);
+ ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
+ stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
+
+ run_native = memnew(EditorRunNative);
+ main_hbox->add_child(run_native);
+ run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native));
+
+ play_scene_button = memnew(Button);
+ main_hbox->add_child(play_scene_button);
+ play_scene_button->set_flat(true);
+ play_scene_button->set_toggle_mode(true);
+ play_scene_button->set_focus_mode(Control::FOCUS_NONE);
+ play_scene_button->set_tooltip_text(TTR("Run the currently edited scene."));
+ play_scene_button->connect("pressed", callable_mp(this, &EditorRunBar::_play_current_pressed));
+
+ ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTR("Run Current Scene"), Key::F6);
+ ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
+ play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
+
+ play_custom_scene_button = memnew(Button);
+ main_hbox->add_child(play_custom_scene_button);
+ play_custom_scene_button->set_flat(true);
+ play_custom_scene_button->set_toggle_mode(true);
+ play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
+ play_custom_scene_button->set_tooltip_text(TTR("Run a specific scene."));
+ play_custom_scene_button->connect("pressed", callable_mp(this, &EditorRunBar::_play_custom_pressed));
+
+ ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTR("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
+ ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
+ play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
+
+ write_movie_panel = memnew(PanelContainer);
+ main_hbox->add_child(write_movie_panel);
+
+ write_movie_button = memnew(Button);
+ write_movie_panel->add_child(write_movie_button);
+ write_movie_button->set_flat(true);
+ write_movie_button->set_toggle_mode(true);
+ write_movie_button->set_pressed(false);
+ write_movie_button->set_focus_mode(Control::FOCUS_NONE);
+ write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
+ write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
+
+ _reset_play_buttons();
+
+ quick_run = memnew(EditorQuickOpen);
+ add_child(quick_run);
+ quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
+}
diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h
new file mode 100644
index 0000000000..b7e7db2bd6
--- /dev/null
+++ b/editor/gui/editor_run_bar.h
@@ -0,0 +1,115 @@
+/**************************************************************************/
+/* editor_run_bar.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 EDITOR_RUN_BAR_H
+#define EDITOR_RUN_BAR_H
+
+#include "editor/editor_run.h"
+#include "editor/export/editor_export.h"
+#include "scene/gui/margin_container.h"
+
+class Button;
+class EditorRunNative;
+class EditorQuickOpen;
+class PanelContainer;
+
+class EditorRunBar : public MarginContainer {
+ GDCLASS(EditorRunBar, MarginContainer);
+
+ static EditorRunBar *singleton;
+
+ enum RunMode {
+ STOPPED = 0,
+ RUN_MAIN,
+ RUN_CURRENT,
+ RUN_CUSTOM,
+ };
+
+ PanelContainer *main_panel = nullptr;
+
+ Button *play_button = nullptr;
+ Button *pause_button = nullptr;
+ Button *stop_button = nullptr;
+ Button *play_scene_button = nullptr;
+ Button *play_custom_scene_button = nullptr;
+
+ EditorRun editor_run;
+ EditorRunNative *run_native = nullptr;
+
+ PanelContainer *write_movie_panel = nullptr;
+ Button *write_movie_button = nullptr;
+
+ EditorQuickOpen *quick_run = nullptr;
+
+ RunMode current_mode = RunMode::STOPPED;
+ String run_custom_filename;
+ String run_current_filename;
+
+ void _reset_play_buttons();
+ void _update_play_buttons();
+
+ void _write_movie_toggled(bool p_enabled);
+ void _quick_run_selected();
+
+ void _play_current_pressed();
+ void _play_custom_pressed();
+
+ void _run_scene(const String &p_scene_path = "");
+ void _run_native(const Ref<EditorExportPreset> &p_preset);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static EditorRunBar *get_singleton() { return singleton; }
+
+ void play_main_scene(bool p_from_native = false);
+ void play_current_scene(bool p_reload = false);
+ void play_custom_scene(const String &p_custom);
+
+ void stop_playing();
+ bool is_playing() const;
+ String get_playing_scene() const;
+
+ Error start_native_device(int p_device_id);
+
+ OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
+ void stop_child_process(OS::ProcessID p_pid);
+
+ void set_movie_maker_enabled(bool p_enabled);
+ bool is_movie_maker_enabled() const;
+
+ Button *get_pause_button() { return pause_button; }
+
+ EditorRunBar();
+};
+
+#endif // EDITOR_RUN_BAR_H
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index ecbf12fed8..6d02fcef1d 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -34,6 +34,7 @@
#include "core/math/expression.h"
#include "core/os/keyboard.h"
#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const {
if (grabber->is_visible()) {
@@ -103,7 +104,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
if (mm->is_shift_pressed() && grabbing_spinner) {
diff_x *= 0.1;
}
- grabbing_spinner_dist_cache += diff_x;
+ grabbing_spinner_dist_cache += diff_x * grabbing_spinner_speed;
if (!grabbing_spinner && ABS(grabbing_spinner_dist_cache) > 4 * EDSCALE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
@@ -439,7 +440,11 @@ void EditorSpinSlider::_draw_spin_slider() {
void EditorSpinSlider::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_ENTER_TREE: {
+ grabbing_spinner_speed = EditorSettings::get_singleton()->get("interface/inspector/float_drag_speed");
+ _update_value_input_stylebox();
+ } break;
+
case NOTIFICATION_THEME_CHANGED: {
_update_value_input_stylebox();
} break;
diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h
index a4d810b18b..8c643157f1 100644
--- a/editor/gui/editor_spin_slider.h
+++ b/editor/gui/editor_spin_slider.h
@@ -60,6 +60,7 @@ class EditorSpinSlider : public Range {
bool read_only = false;
float grabbing_spinner_dist_cache = 0.0f;
+ float grabbing_spinner_speed = 0.0f;
Vector2 grabbing_spinner_mouse_pos;
double pre_grab_value = 0.0;
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
index e105135c11..6214a2b70d 100644
--- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
@@ -669,7 +669,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
StringName bn = skin->get_bind_name(i);
int bone_idx = src_skeleton->find_bone(bn);
if (bone_idx >= 0) {
- Transform3D new_rest = silhouette_diff[i] * src_skeleton->get_bone_global_rest(bone_idx);
+ Transform3D new_rest = silhouette_diff[bone_idx] * src_skeleton->get_bone_global_rest(bone_idx);
skin->set_bind_pose(i, new_rest.inverse() * ibm_diff[bone_idx]);
}
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index eef1fea11b..f9ab37dce2 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -38,6 +38,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_toaster.h"
#include "editor/gui/editor_zoom_widget.h"
#include "editor/plugins/animation_player_editor_plugin.h"
@@ -4975,8 +4976,8 @@ CanvasItemEditor::CanvasItemEditor() {
SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created));
SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));
- EditorNode::get_singleton()->call_deferred(SNAME("connect"), "play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
- EditorNode::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
+ EditorRunBar::get_singleton()->call_deferred(SNAME("connect"), "play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
+ EditorRunBar::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
// A fluid container for all toolbars.
HFlowContainer *main_flow = memnew(HFlowContainer);
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp
index 4afbb87197..64a3346224 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.cpp
+++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp
@@ -42,6 +42,7 @@
#include "scene/resources/segment_shape_2d.h"
#include "scene/resources/separation_ray_shape_2d.h"
#include "scene/resources/world_boundary_shape_2d.h"
+#include "scene/scene_string_names.h"
void CollisionShape2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
@@ -129,8 +130,6 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} else if (idx == 1) {
capsule->set_height(parameter * 2);
}
-
- canvas_item_editor->update_viewport();
}
} break;
@@ -138,9 +137,6 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
case CIRCLE_SHAPE: {
Ref<CircleShape2D> circle = node->get_shape();
circle->set_radius(p_point.length());
-
- canvas_item_editor->update_viewport();
-
} break;
case CONCAVE_POLYGON_SHAPE: {
@@ -158,19 +154,13 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} else {
world_boundary->set_normal(p_point.normalized());
}
-
- canvas_item_editor->update_viewport();
}
-
} break;
case SEPARATION_RAY_SHAPE: {
Ref<SeparationRayShape2D> ray = node->get_shape();
ray->set_length(Math::abs(p_point.y));
-
- canvas_item_editor->update_viewport();
-
} break;
case RECTANGLE_SHAPE: {
@@ -194,8 +184,6 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
pos += (size - (Point2)original) * 0.5 * RECT_HANDLES[idx] * 0.5;
node->set_global_position(original_transform.xform(pos));
}
-
- canvas_item_editor->update_viewport();
}
} break;
@@ -209,13 +197,9 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} else if (idx == 1) {
seg->set_b(p_point);
}
-
- canvas_item_editor->update_viewport();
}
-
} break;
}
- node->get_shape()->notify_property_list_changed();
}
void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
@@ -233,10 +217,8 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
} else if (idx == 1) {
undo_redo->add_do_method(capsule.ptr(), "set_height", capsule->get_height());
}
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(capsule.ptr(), "set_radius", values[0]);
undo_redo->add_undo_method(capsule.ptr(), "set_height", values[1]);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} break;
@@ -244,9 +226,7 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
Ref<CircleShape2D> circle = node->get_shape();
undo_redo->add_do_method(circle.ptr(), "set_radius", circle->get_radius());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(circle.ptr(), "set_radius", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} break;
@@ -263,14 +243,10 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
if (idx == 0) {
undo_redo->add_do_method(world_boundary.ptr(), "set_distance", world_boundary->get_distance());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(world_boundary.ptr(), "set_distance", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} else {
undo_redo->add_do_method(world_boundary.ptr(), "set_normal", world_boundary->get_normal());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(world_boundary.ptr(), "set_normal", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
}
} break;
@@ -279,9 +255,7 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
Ref<SeparationRayShape2D> ray = node->get_shape();
undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(ray.ptr(), "set_length", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} break;
@@ -290,10 +264,8 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
undo_redo->add_do_method(rect.ptr(), "set_size", rect->get_size());
undo_redo->add_do_method(node, "set_global_transform", node->get_global_transform());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(rect.ptr(), "set_size", p_org);
undo_redo->add_undo_method(node, "set_global_transform", original_transform);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} break;
@@ -301,14 +273,10 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
Ref<SegmentShape2D> seg = node->get_shape();
if (idx == 0) {
undo_redo->add_do_method(seg.ptr(), "set_a", seg->get_a());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(seg.ptr(), "set_a", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} else if (idx == 1) {
undo_redo->add_do_method(seg.ptr(), "set_b", seg->get_b());
- undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(seg.ptr(), "set_b", p_org);
- undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
}
} break;
@@ -322,10 +290,6 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
return false;
}
- if (!node->get_shape().is_valid()) {
- return false;
- }
-
if (!node->is_visible_in_tree()) {
return false;
}
@@ -410,38 +374,44 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
return false;
}
-void CollisionShape2DEditor::_get_current_shape_type() {
+void CollisionShape2DEditor::_shape_changed() {
+ canvas_item_editor->update_viewport();
+
+ if (current_shape.is_valid()) {
+ current_shape->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(canvas_item_editor, &CanvasItemEditor::update_viewport));
+ current_shape = Ref<Shape2D>();
+ shape_type = -1;
+ }
+
if (!node) {
return;
}
- Ref<Shape2D> s = node->get_shape();
+ current_shape = node->get_shape();
- if (!s.is_valid()) {
+ if (current_shape.is_valid()) {
+ current_shape->connect(SceneStringNames::get_singleton()->changed, callable_mp(canvas_item_editor, &CanvasItemEditor::update_viewport));
+ } else {
return;
}
- if (Object::cast_to<CapsuleShape2D>(*s)) {
+ if (Object::cast_to<CapsuleShape2D>(*current_shape)) {
shape_type = CAPSULE_SHAPE;
- } else if (Object::cast_to<CircleShape2D>(*s)) {
+ } else if (Object::cast_to<CircleShape2D>(*current_shape)) {
shape_type = CIRCLE_SHAPE;
- } else if (Object::cast_to<ConcavePolygonShape2D>(*s)) {
+ } else if (Object::cast_to<ConcavePolygonShape2D>(*current_shape)) {
shape_type = CONCAVE_POLYGON_SHAPE;
- } else if (Object::cast_to<ConvexPolygonShape2D>(*s)) {
+ } else if (Object::cast_to<ConvexPolygonShape2D>(*current_shape)) {
shape_type = CONVEX_POLYGON_SHAPE;
- } else if (Object::cast_to<WorldBoundaryShape2D>(*s)) {
+ } else if (Object::cast_to<WorldBoundaryShape2D>(*current_shape)) {
shape_type = WORLD_BOUNDARY_SHAPE;
- } else if (Object::cast_to<SeparationRayShape2D>(*s)) {
+ } else if (Object::cast_to<SeparationRayShape2D>(*current_shape)) {
shape_type = SEPARATION_RAY_SHAPE;
- } else if (Object::cast_to<RectangleShape2D>(*s)) {
+ } else if (Object::cast_to<RectangleShape2D>(*current_shape)) {
shape_type = RECTANGLE_SHAPE;
- } else if (Object::cast_to<SegmentShape2D>(*s)) {
+ } else if (Object::cast_to<SegmentShape2D>(*current_shape)) {
shape_type = SEGMENT_SHAPE;
- } else {
- shape_type = -1;
}
-
- canvas_item_editor->update_viewport();
}
void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
@@ -449,16 +419,10 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
return;
}
- if (!node->get_shape().is_valid()) {
- return;
- }
-
if (!node->is_visible_in_tree()) {
return;
}
- _get_current_shape_type();
-
if (shape_type == -1) {
return;
}
@@ -559,6 +523,12 @@ void CollisionShape2DEditor::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
get_tree()->disconnect("node_removed", callable_mp(this, &CollisionShape2DEditor::_node_removed));
} break;
+
+ case NOTIFICATION_PROCESS: {
+ if (node && node->get_shape() != current_shape) {
+ _shape_changed();
+ }
+ } break;
}
}
@@ -569,26 +539,17 @@ void CollisionShape2DEditor::edit(Node *p_node) {
if (p_node) {
node = Object::cast_to<CollisionShape2D>(p_node);
-
- _get_current_shape_type();
-
+ set_process(true);
} else {
if (pressed) {
set_handle(edit_handle, original_point);
pressed = false;
}
-
edit_handle = -1;
- shape_type = -1;
-
node = nullptr;
+ set_process(false);
}
-
- canvas_item_editor->update_viewport();
-}
-
-void CollisionShape2DEditor::_bind_methods() {
- ClassDB::bind_method("_get_current_shape_type", &CollisionShape2DEditor::_get_current_shape_type);
+ _shape_changed();
}
CollisionShape2DEditor::CollisionShape2DEditor() {
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h
index 9c37b6cf9d..749245ae28 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.h
+++ b/editor/plugins/collision_shape_2d_editor_plugin.h
@@ -74,16 +74,17 @@ class CollisionShape2DEditor : public Control {
Vector2 original_point;
Point2 last_point;
+ Ref<Shape2D> current_shape;
+
Variant get_handle_value(int idx) const;
void set_handle(int idx, Point2 &p_point);
void commit_handle(int idx, Variant &p_org);
- void _get_current_shape_type();
+ void _shape_changed();
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
- static void _bind_methods();
public:
bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index a7ec4bfe33..ba2d7e67bf 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -39,6 +39,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "scene/gui/popup_menu.h"
CurveEditor::CurveEditor() {
_selected_point = -1;
diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h
index d5cc0cb66a..ca1a824f0c 100644
--- a/editor/plugins/curve_editor_plugin.h
+++ b/editor/plugins/curve_editor_plugin.h
@@ -36,6 +36,8 @@
#include "editor/editor_resource_preview.h"
#include "scene/resources/curve.h"
+class PopupMenu;
+
// Edits a y(x) curve
class CurveEditor : public Control {
GDCLASS(CurveEditor, Control);
diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp
index 328fe9b950..2fdebc7c7f 100644
--- a/editor/plugins/material_editor_plugin.cpp
+++ b/editor/plugins/material_editor_plugin.cpp
@@ -42,6 +42,7 @@
#include "scene/gui/color_rect.h"
#include "scene/gui/subviewport_container.h"
#include "scene/gui/texture_button.h"
+#include "scene/main/viewport.h"
#include "scene/resources/fog_material.h"
#include "scene/resources/particle_process_material.h"
#include "scene/resources/sky_material.h"
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 00382e1a18..b819145e67 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -40,6 +40,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_spin_slider.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/gizmos/audio_listener_3d_gizmo_plugin.h"
@@ -7518,8 +7519,8 @@ void Node3DEditor::_notification(int p_what) {
SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
- EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false));
- EditorNode::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true));
+ EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false));
+ EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true));
_update_preview_environment();
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 736826c231..e928abe00e 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -43,12 +43,13 @@
#include "editor/editor_help_search.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_run_script.h"
#include "editor/editor_scale.h"
+#include "editor/editor_script.h"
#include "editor/editor_settings.h"
#include "editor/filesystem_dock.h"
#include "editor/find_in_files.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/inspector_dock.h"
#include "editor/node_dock.h"
#include "editor/plugins/shader_editor_plugin.h"
@@ -1375,9 +1376,7 @@ void ScriptEditor::_menu_option(int p_option) {
Ref<EditorScript> es = memnew(EditorScript);
es->set_script(scr);
- es->set_editor(EditorNode::get_singleton());
-
- es->_run();
+ es->run();
} break;
case FILE_CLOSE: {
if (current->is_unsaved()) {
@@ -1592,7 +1591,7 @@ void ScriptEditor::_tab_changed(int p_which) {
void ScriptEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
+ EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback));
EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback));
EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback));
@@ -1646,7 +1645,7 @@ void ScriptEditor::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
- EditorNode::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
+ EditorRunBar::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
@@ -3317,7 +3316,7 @@ void ScriptEditor::_help_class_open(const String &p_class) {
eh->set_name(p_class);
tab_container->add_child(eh);
_go_to_tab(tab_container->get_tab_count() - 1);
- eh->go_to_class(p_class, 0);
+ eh->go_to_class(p_class);
eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
_add_recent_script(p_class);
_sort_list_on_update = true;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 837645310a..65b7526722 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -38,6 +38,7 @@
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/split_container.h"
void ConnectionInfoDialog::ok_pressed() {
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 1cd863fb8a..95cf9496dc 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -36,6 +36,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/export/editor_export.h"
#include "scene/gui/check_button.h"
#include "servers/movie_writer/movie_writer.h"
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 724d435f68..0a63c40bf6 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -30,7 +30,6 @@
#include "register_editor_types.h"
-#include "editor/animation_track_editor.h"
#include "editor/debugger/debug_adapter/debug_adapter_server.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
@@ -40,11 +39,13 @@
#include "editor/editor_paths.h"
#include "editor/editor_resource_picker.h"
#include "editor/editor_resource_preview.h"
-#include "editor/editor_run_script.h"
+#include "editor/editor_script.h"
#include "editor/editor_settings.h"
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/export/editor_export_platform.h"
#include "editor/export/editor_export_platform_pc.h"
+#include "editor/export/editor_export_plugin.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_spin_slider.h"
@@ -137,14 +138,15 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorExportPlugin);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC);
+
register_exporter_types();
+
GDREGISTER_CLASS(EditorResourceConversionPlugin);
GDREGISTER_CLASS(EditorSceneFormatImporter);
GDREGISTER_CLASS(EditorScenePostImportPlugin);
GDREGISTER_CLASS(EditorInspector);
GDREGISTER_CLASS(EditorInspectorPlugin);
GDREGISTER_CLASS(EditorProperty);
- GDREGISTER_CLASS(AnimationTrackEditPlugin);
GDREGISTER_CLASS(ScriptCreateDialog);
GDREGISTER_CLASS(EditorFeatureProfile);
GDREGISTER_CLASS(EditorSpinSlider);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 968cd5ab23..96166dab3f 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -53,6 +53,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "editor/reparent_dialog.h"
#include "editor/shader_create_dialog.h"
+#include "scene/gui/check_box.h"
#include "scene/main/window.h"
#include "scene/property_utils.h"
#include "scene/resources/packed_scene.h"
@@ -850,9 +851,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
}
- if (p_confirm_override) {
+ bool allow_ask_delete_tracks = EDITOR_GET("docks/scene_tree/ask_before_deleting_related_animation_tracks").operator bool();
+ bool has_tracks_to_delete = allow_ask_delete_tracks && _has_tracks_to_delete(edited_scene, remove_list);
+ if (p_confirm_override && !has_tracks_to_delete) {
_delete_confirm();
-
} else {
String msg;
if (remove_list.size() > 1) {
@@ -863,18 +865,30 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
msg = vformat(any_children ? TTR("Delete %d nodes and any children?") : TTR("Delete %d nodes?"), remove_list.size());
} else {
- Node *node = remove_list[0];
- if (node == editor_data->get_edited_scene_root()) {
- msg = vformat(TTR("Delete the root node \"%s\"?"), node->get_name());
- } else if (node->get_scene_file_path().is_empty() && node->get_child_count() > 0) {
- // Display this message only for non-instantiated scenes
- msg = vformat(TTR("Delete node \"%s\" and its children?"), node->get_name());
+ if (!p_confirm_override) {
+ Node *node = remove_list[0];
+ if (node == editor_data->get_edited_scene_root()) {
+ msg = vformat(TTR("Delete the root node \"%s\"?"), node->get_name());
+ } else if (node->get_scene_file_path().is_empty() && node->get_child_count() > 0) {
+ // Display this message only for non-instantiated scenes
+ msg = vformat(TTR("Delete node \"%s\" and its children?"), node->get_name());
+ } else {
+ msg = vformat(TTR("Delete node \"%s\"?"), node->get_name());
+ }
+ }
+
+ if (has_tracks_to_delete) {
+ if (!msg.is_empty()) {
+ msg += "\n";
+ }
+ msg += TTR("Some nodes are referenced by animation tracks.");
+ delete_tracks_checkbox->show();
} else {
- msg = vformat(TTR("Delete node \"%s\"?"), node->get_name());
+ delete_tracks_checkbox->hide();
}
}
- delete_dialog->set_text(msg);
+ delete_dialog_label->set_text(msg);
// Resize the dialog to its minimum size.
// This prevents the dialog from being too wide after displaying
@@ -1496,12 +1510,10 @@ void SceneTreeDock::_set_owners(Node *p_owner, const Array &p_nodes) {
void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, HashMap<Node *, NodePath> *p_renames) {
base_path.push_back(p_node->get_name());
- if (new_base_path.size()) {
- new_base_path.push_back(p_node->get_name());
- }
NodePath new_path;
- if (new_base_path.size()) {
+ if (!new_base_path.is_empty()) {
+ new_base_path.push_back(p_node->get_name());
new_path = NodePath(new_base_path, true);
}
@@ -1512,6 +1524,43 @@ void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<Stri
}
}
+bool SceneTreeDock::_has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
+ if (ap) {
+ Node *root = ap->get_node(ap->get_root());
+ if (root && !p_to_delete.find(root)) {
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+
+ for (const StringName &E : anims) {
+ Ref<Animation> anim = ap->get_animation(E);
+ if (anim.is_null()) {
+ continue;
+ }
+
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ NodePath track_np = anim->track_get_path(i);
+ Node *n = root->get_node_or_null(track_np);
+ if (n) {
+ for (const Node *F : p_to_delete) {
+ if (F == n || F->is_ancestor_of(n)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ if (_has_tracks_to_delete(p_node->get_child(i), p_to_delete)) {
+ return true;
+ }
+ }
+ return false;
+}
+
void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, HashMap<Node *, NodePath> *p_renames) {
Vector<StringName> base_path;
Node *n = p_node->get_parent();
@@ -1701,7 +1750,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap<Node *, NodePath>
HashMap<Node *, NodePath>::Iterator found_path = p_renames->find(n);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (found_path) {
- if (found_path->value == NodePath()) {
+ if (found_path->value.is_empty()) {
//will be erased
int idx = 0;
@@ -2094,11 +2143,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
return;
}
- EditorNode::get_singleton()->hide_unused_editors(this);
-
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(p_cut ? TTR("Cut Node(s)") : TTR("Remove Node(s)"), UndoRedo::MERGE_DISABLE, remove_list.front()->get());
-
bool entire_scene = false;
for (const Node *E : remove_list) {
@@ -2108,27 +2152,34 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
}
+ EditorNode::get_singleton()->hide_unused_editors(this);
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(p_cut ? TTR("Cut Node(s)") : TTR("Remove Node(s)"), UndoRedo::MERGE_DISABLE, remove_list.front()->get());
+
if (entire_scene) {
undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", edited_scene);
undo_redo->add_undo_method(edited_scene, "set_owner", edited_scene->get_owner());
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->add_undo_reference(edited_scene);
-
} else {
- remove_list.sort_custom<Node::Comparator>(); //sort nodes to keep positions
- HashMap<Node *, NodePath> path_renames;
+ if (delete_tracks_checkbox->is_pressed() || p_cut) {
+ remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions.
+ HashMap<Node *, NodePath> path_renames;
- //delete from animation
- for (Node *n : remove_list) {
- if (!n->is_inside_tree() || !n->get_parent()) {
- continue;
+ //delete from animation
+ for (Node *n : remove_list) {
+ if (!n->is_inside_tree() || !n->get_parent()) {
+ continue;
+ }
+
+ fill_path_renames(n, nullptr, &path_renames);
}
- fill_path_renames(n, nullptr, &path_renames);
+ perform_node_renames(nullptr, &path_renames);
}
- perform_node_renames(nullptr, &path_renames);
//delete for read
for (Node *n : remove_list) {
if (!n->is_inside_tree() || !n->get_parent()) {
@@ -3774,6 +3825,16 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
add_child(delete_dialog);
delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm).bind(false));
+ VBoxContainer *vb = memnew(VBoxContainer);
+ delete_dialog->add_child(vb);
+
+ delete_dialog_label = memnew(Label);
+ vb->add_child(delete_dialog_label);
+
+ delete_tracks_checkbox = memnew(CheckBox(TTR("Delete Related Animation Tracks")));
+ delete_tracks_checkbox->set_pressed(true);
+ vb->add_child(delete_tracks_checkbox);
+
editable_instance_remove_dialog = memnew(ConfirmationDialog);
add_child(editable_instance_remove_dialog);
editable_instance_remove_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_toggle_editable_children_from_selection));
@@ -3810,6 +3871,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true);
EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
+ EDITOR_DEF("docks/scene_tree/ask_before_deleting_related_animation_tracks", true);
EDITOR_DEF("_use_favorites_root_selection", false);
Resource::_update_configuration_warning = _update_configuration_warning;
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 75acb37973..e8a6686386 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -36,6 +36,7 @@
#include "scene/gui/box_container.h"
#include "scene/resources/animation.h"
+class CheckBox;
class EditorData;
class EditorSelection;
class EditorQuickOpen;
@@ -148,6 +149,8 @@ class SceneTreeDock : public VBoxContainer {
ShaderCreateDialog *shader_create_dialog = nullptr;
AcceptDialog *accept = nullptr;
ConfirmationDialog *delete_dialog = nullptr;
+ Label *delete_dialog_label = nullptr;
+ CheckBox *delete_tracks_checkbox = nullptr;
ConfirmationDialog *editable_instance_remove_dialog = nullptr;
ConfirmationDialog *placeholder_editable_instance_remove_dialog = nullptr;
@@ -213,6 +216,7 @@ class SceneTreeDock : public VBoxContainer {
void _shader_creation_closed();
void _delete_confirm(bool p_cut = false);
+ void _delete_dialog_closed();
void _toggle_editable_children_from_selection();
void _toggle_editable_children(Node *p_node);
@@ -234,6 +238,7 @@ class SceneTreeDock : public VBoxContainer {
void _update_script_button();
void _fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, HashMap<Node *, NodePath> *p_renames);
+ bool _has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const;
void _normalize_drop(Node *&to_node, int &to_pos, int p_type);
diff --git a/main/main.cpp b/main/main.cpp
index bc309219f4..5e0187cc7f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -167,7 +167,7 @@ static bool project_manager = false;
static bool cmdline_tool = false;
static String locale;
static bool show_help = false;
-static bool auto_quit = false;
+static uint64_t quit_after = 0;
static OS::ProcessID editor_pid = 0;
#ifdef TOOLS_ENABLED
static bool found_project = false;
@@ -355,6 +355,7 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --debug-server <uri> Start the editor debug server (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007)\n");
#endif
OS::get_singleton()->print(" --quit Quit after the first iteration.\n");
+ OS::get_singleton()->print(" --quit-after <int> Quit after the given number of iterations. Set to 0 to disable.\n");
OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n");
OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n");
OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n");
@@ -398,6 +399,7 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --write-movie <file> Writes a video to the specified path (usually with .avi or .png extension).\n");
OS::get_singleton()->print(" --fixed-fps is forced when enabled, but it can be used to change movie FPS.\n");
OS::get_singleton()->print(" --disable-vsync can speed up movie writing but makes interaction more difficult.\n");
+ OS::get_singleton()->print(" --quit-after can be used to specify the number of frames to write.\n");
OS::get_singleton()->print("\n");
@@ -471,6 +473,8 @@ void Main::print_help(const char *p_binary) {
// The order is the same as in `Main::setup()`, only core and some editor types
// are initialized here. This also combines `Main::setup2()` initialization.
Error Main::test_setup() {
+ Thread::make_main_thread();
+
OS::get_singleton()->initialize();
engine = memnew(Engine);
@@ -680,6 +684,8 @@ int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) {
*/
Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_phase) {
+ Thread::make_main_thread();
+
OS::get_singleton()->initialize();
engine = memnew(Engine);
@@ -1196,7 +1202,15 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "-u" || I->get() == "--upwards") { // scan folders upwards
upwards = true;
} else if (I->get() == "--quit") { // Auto quit at the end of the first main loop iteration
- auto_quit = true;
+ quit_after = 1;
+ } else if (I->get() == "--quit-after") { // Quit after the given number of iterations
+ if (I->next()) {
+ quit_after = I->next()->get().to_int();
+ N = I->next()->next();
+ } else {
+ OS::get_singleton()->print("Missing number of iterations, aborting.\n");
+ goto error;
+ }
} else if (I->get().ends_with("project.godot")) {
String path;
String file = I->get();
@@ -1876,6 +1890,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
engine->startup_benchmark_end_measure(); // core
+ Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one.
+
if (p_second_phase) {
return setup2();
}
@@ -1941,7 +1957,9 @@ error:
return exit_code;
}
-Error Main::setup2(Thread::ID p_main_tid_override) {
+Error Main::setup2() {
+ Thread::make_main_thread(); // Make whatever thread call this the main thread.
+
// Print engine name and version
print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE));
@@ -1962,10 +1980,6 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
initialize_modules(MODULE_INITIALIZATION_LEVEL_SERVERS);
GDExtensionManager::get_singleton()->initialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS);
- if (p_main_tid_override) {
- Thread::main_thread_id = p_main_tid_override;
- }
-
#ifdef TOOLS_ENABLED
if (editor || project_manager || cmdline_tool) {
EditorPaths::create();
@@ -3297,6 +3311,10 @@ bool Main::iteration() {
movie_writer->add_frame(vp_tex);
}
+ if ((quit_after > 0) && (Engine::get_singleton()->_process_frames >= quit_after)) {
+ exit = true;
+ }
+
if (fixed_fps != -1) {
return exit;
}
@@ -3320,7 +3338,7 @@ bool Main::iteration() {
}
#endif
- return exit || auto_quit;
+ return exit;
}
void Main::force_redraw() {
diff --git a/main/main.h b/main/main.h
index e345589f59..cc0655cd02 100644
--- a/main/main.h
+++ b/main/main.h
@@ -60,7 +60,7 @@ public:
static int test_entrypoint(int argc, char *argv[], bool &tests_need_run);
static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true);
- static Error setup2(Thread::ID p_main_tid_override = 0);
+ static Error setup2(); // The thread calling setup2() will effectively become the main thread.
static String get_rendering_driver_name();
#ifdef TESTS_ENABLED
static Error test_setup();
diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp
index dee95d1ac5..a8ae4cfa5e 100644
--- a/modules/csg/csg.cpp
+++ b/modules/csg/csg.cpp
@@ -526,17 +526,19 @@ int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *facebvhptr, FaceBVH **fac
return index;
}
-void CSGBrushOperation::MeshMerge::_add_distance(List<real_t> &r_intersectionsA, List<real_t> &r_intersectionsB, bool p_from_B, real_t p_distance) const {
- List<real_t> &intersections = p_from_B ? r_intersectionsB : r_intersectionsA;
+void CSGBrushOperation::MeshMerge::_add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance_squared, bool p_is_conormal) const {
+ List<IntersectionDistance> &intersections = p_from_B ? r_intersectionsB : r_intersectionsA;
// Check if distance exists.
- for (const real_t E : intersections) {
- if (Math::is_equal_approx(E, p_distance)) {
+ for (const IntersectionDistance E : intersections) {
+ if (E.is_conormal == p_is_conormal && Math::is_equal_approx(E.distance_squared, p_distance_squared)) {
return;
}
}
-
- intersections.push_back(p_distance);
+ IntersectionDistance IntersectionDistance;
+ IntersectionDistance.is_conormal = p_is_conormal;
+ IntersectionDistance.distance_squared = p_distance_squared;
+ intersections.push_back(IntersectionDistance);
}
bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const {
@@ -561,8 +563,11 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
VISITED_BIT_MASK = ~NODE_IDX_MASK
};
- List<real_t> intersectionsA;
- List<real_t> intersectionsB;
+ List<IntersectionDistance> intersectionsA;
+ List<IntersectionDistance> intersectionsB;
+
+ Intersection closest_intersection;
+ closest_intersection.found = false;
int level = 0;
int pos = p_bvh_first;
@@ -587,17 +592,61 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
};
Vector3 current_normal = Plane(current_points[0], current_points[1], current_points[2]).normal;
Vector3 intersection_point;
-
// Check if faces are co-planar.
if (current_normal.is_equal_approx(face_normal) &&
is_point_in_triangle(face_center, current_points)) {
// Only add an intersection if not a B face.
if (!face.from_b) {
- _add_distance(intersectionsA, intersectionsB, current_face.from_b, 0);
+ _add_distance(intersectionsA, intersectionsB, current_face.from_b, 0, true);
}
} else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) {
- real_t distance = face_center.distance_to(intersection_point);
- _add_distance(intersectionsA, intersectionsB, current_face.from_b, distance);
+ real_t distance_squared = face_center.distance_squared_to(intersection_point);
+ real_t inner = current_normal.dot(face_normal);
+ // If the faces are perpendicular, ignore this face.
+ // The triangles on the side should be intersected and result in the correct behavior.
+ if (!Math::is_zero_approx(inner)) {
+ _add_distance(intersectionsA, intersectionsB, current_face.from_b, distance_squared, inner > 0.0f);
+ }
+ }
+
+ if (face.from_b != current_face.from_b) {
+ if (current_normal.is_equal_approx(face_normal) &&
+ is_point_in_triangle(face_center, current_points)) {
+ // Only add an intersection if not a B face.
+ if (!face.from_b) {
+ closest_intersection.found = true;
+ closest_intersection.conormal = 1.0f;
+ closest_intersection.distance_squared = 0.0f;
+ closest_intersection.origin_angle = -FLT_MAX;
+ }
+ } else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) {
+ Intersection potential_intersection;
+ potential_intersection.found = true;
+ potential_intersection.conormal = face_normal.dot(current_normal);
+ potential_intersection.distance_squared = face_center.distance_squared_to(intersection_point);
+ potential_intersection.origin_angle = Math::abs(potential_intersection.conormal);
+ real_t intersection_dist_from_face = face_normal.dot(intersection_point - face_center);
+ for (int i = 0; i < 3; i++) {
+ real_t point_dist_from_face = face_normal.dot(current_points[i] - face_center);
+ if (!Math::is_equal_approx(point_dist_from_face, intersection_dist_from_face) &&
+ point_dist_from_face < intersection_dist_from_face) {
+ potential_intersection.origin_angle = -potential_intersection.origin_angle;
+ break;
+ }
+ }
+ if (potential_intersection.conormal != 0.0f) {
+ if (!closest_intersection.found) {
+ closest_intersection = potential_intersection;
+ } else if (!Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared) &&
+ potential_intersection.distance_squared < closest_intersection.distance_squared) {
+ closest_intersection = potential_intersection;
+ } else if (Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared)) {
+ if (potential_intersection.origin_angle < closest_intersection.origin_angle) {
+ closest_intersection = potential_intersection;
+ }
+ }
+ }
+ }
}
}
@@ -652,8 +701,11 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
}
}
- // Inside if face normal intersects other faces an odd number of times.
- return (intersectionsA.size() + intersectionsB.size()) & 1;
+ if (!closest_intersection.found) {
+ return false;
+ } else {
+ return closest_intersection.conormal > 0.0f;
+ }
}
void CSGBrushOperation::MeshMerge::mark_inside_faces() {
diff --git a/modules/csg/csg.h b/modules/csg/csg.h
index 1513a01f9e..473a23e39f 100644
--- a/modules/csg/csg.h
+++ b/modules/csg/csg.h
@@ -135,6 +135,17 @@ struct CSGBrushOperation {
return h;
}
};
+ struct Intersection {
+ bool found = false;
+ real_t conormal = FLT_MAX;
+ real_t distance_squared = FLT_MAX;
+ real_t origin_angle = FLT_MAX;
+ };
+
+ struct IntersectionDistance {
+ bool is_conormal;
+ real_t distance_squared;
+ };
Vector<Vector3> points;
Vector<Face> faces;
@@ -143,7 +154,7 @@ struct CSGBrushOperation {
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
float vertex_snap = 0.0;
- inline void _add_distance(List<real_t> &r_intersectionsA, List<real_t> &r_intersectionsB, bool p_from_B, real_t p_distance) const;
+ inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
inline bool _bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
inline int _create_bvh(FaceBVH *facebvhptr, FaceBVH **facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index afb8e62eea..ba0d49c993 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -653,7 +653,7 @@ void CSGShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.0001,1,0.001,suffix:m"), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
ADD_GROUP("Collision", "collision_");
diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml
index e92bb05b51..cae30ed446 100644
--- a/modules/csg/doc_classes/CSGShape3D.xml
+++ b/modules/csg/doc_classes/CSGShape3D.xml
@@ -15,14 +15,14 @@
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_collision_mask_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_meshes" qualifiers="const">
@@ -42,7 +42,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_layer], given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_collision_mask_value">
@@ -50,7 +50,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
</description>
</method>
</methods>
@@ -73,7 +73,7 @@
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
</member>
<member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.001">
- Snap makes the mesh snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust.
+ Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust.
</member>
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].
diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml
index 365d12bb7a..3c1f559b9f 100644
--- a/modules/enet/doc_classes/ENetConnection.xml
+++ b/modules/enet/doc_classes/ENetConnection.xml
@@ -24,7 +24,7 @@
<param index="1" name="packet" type="PackedByteArray" />
<param index="2" name="flags" type="int" />
<description>
- Queues a [code]packet[/code] to be sent to all peers associated with the host over the specified [code]channel[/code]. See [ENetPacketPeer] [code]FLAG_*[/code] constants for available packet flags.
+ Queues a [param packet] to be sent to all peers associated with the host over the specified [param channel]. See [ENetPacketPeer] [code]FLAG_*[/code] constants for available packet flags.
</description>
</method>
<method name="channel_limit">
@@ -50,7 +50,7 @@
<param index="2" name="channels" type="int" default="0" />
<param index="3" name="data" type="int" default="0" />
<description>
- Initiates a connection to a foreign [code]address[/code] using the specified [code]port[/code] and allocating the requested [code]channels[/code]. Optional [code]data[/code] can be passed during connection in the form of a 32 bit integer.
+ Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer.
[b]Note:[/b] You must call either [method create_host] or [method create_host_bound] before calling this method.
</description>
</method>
@@ -61,7 +61,7 @@
<param index="2" name="in_bandwidth" type="int" default="0" />
<param index="3" name="out_bandwidth" type="int" default="0" />
<description>
- Create an ENetHost that will allow up to [code]max_peers[/code] connected peers, each allocating up to [code]max_channels[/code] channels, optionally limiting bandwidth to [code]in_bandwidth[/code] and [code]out_bandwidth[/code].
+ Create an ENetHost that will allow up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth].
</description>
</method>
<method name="create_host_bound">
@@ -73,7 +73,7 @@
<param index="4" name="in_bandwidth" type="int" default="0" />
<param index="5" name="out_bandwidth" type="int" default="0" />
<description>
- Create an ENetHost like [method create_host] which is also bound to the given [code]bind_address[/code] and [code]bind_port[/code].
+ Create an ENetHost like [method create_host] which is also bound to the given [param bind_address] and [param bind_port].
</description>
</method>
<method name="destroy">
@@ -87,7 +87,7 @@
<param index="0" name="hostname" type="String" />
<param index="1" name="client_options" type="TLSOptions" default="null" />
<description>
- Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [code]hostname[/code]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
+ Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [param hostname]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="dtls_server_setup">
diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
index 0e81244a1c..646cf37b55 100644
--- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml
+++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
@@ -17,8 +17,8 @@
<param index="0" name="peer_id" type="int" />
<param index="1" name="host" type="ENetConnection" />
<description>
- Add a new remote peer with the given [code]peer_id[/code] connected to the given [code]host[/code].
- [b]Note:[/b] The [code]host[/code] must have exactly one peer in the [constant ENetPacketPeer.STATE_CONNECTED] state.
+ Add a new remote peer with the given [param peer_id] connected to the given [param host].
+ [b]Note:[/b] The [param host] must have exactly one peer in the [constant ENetPacketPeer.STATE_CONNECTED] state.
</description>
</method>
<method name="create_client">
@@ -30,14 +30,14 @@
<param index="4" name="out_bandwidth" type="int" default="0" />
<param index="5" name="local_port" type="int" default="0" />
<description>
- Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [code]port[/code] is the port the server is listening on. The [code]channel_count[/code] parameter can be used to specify the number of ENet channels allocated for the connection. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [code]local_port[/code] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques.
+ Create client that connects to a server at [param address] using specified [param port]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [param port] is the port the server is listening on. The [param channel_count] parameter can be used to specify the number of ENet channels allocated for the connection. The [param in_bandwidth] and [param out_bandwidth] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [param local_port] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques.
</description>
</method>
<method name="create_mesh">
<return type="int" enum="Error" />
<param index="0" name="unique_id" type="int" />
<description>
- Initialize this [MultiplayerPeer] in mesh mode. The provided [code]unique_id[/code] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.multiplayer_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server).
+ Initialize this [MultiplayerPeer] in mesh mode. The provided [param unique_id] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.multiplayer_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server).
</description>
</method>
<method name="create_server">
@@ -48,14 +48,14 @@
<param index="3" name="in_bandwidth" type="int" default="0" />
<param index="4" name="out_bandwidth" type="int" default="0" />
<description>
- Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the server could not be created.
+ Create server that listens to connections via [param port]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [param max_clients] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the server could not be created.
</description>
</method>
<method name="get_peer" qualifiers="const">
<return type="ENetPacketPeer" />
<param index="0" name="id" type="int" />
<description>
- Returns the [ENetPacketPeer] associated to the given [code]id[/code].
+ Returns the [ENetPacketPeer] associated to the given [param id].
</description>
</method>
<method name="set_bind_ip">
diff --git a/modules/enet/doc_classes/ENetPacketPeer.xml b/modules/enet/doc_classes/ENetPacketPeer.xml
index 9c7ee3c17d..0e531b0e89 100644
--- a/modules/enet/doc_classes/ENetPacketPeer.xml
+++ b/modules/enet/doc_classes/ENetPacketPeer.xml
@@ -40,7 +40,7 @@
<return type="float" />
<param index="0" name="statistic" type="int" enum="ENetPacketPeer.PeerStatistic" />
<description>
- Returns the requested [code]statistic[/code] for this peer. See [enum PeerStatistic].
+ Returns the requested [param statistic] for this peer. See [enum PeerStatistic].
</description>
</method>
<method name="is_active" qualifiers="const">
@@ -80,7 +80,7 @@
<return type="void" />
<param index="0" name="ping_interval" type="int" />
<description>
- Sets the [code]ping_interval[/code] in milliseconds at which pings will be sent to a peer. Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes. The default ping interval is [code]500[/code] milliseconds.
+ Sets the [param ping_interval] in milliseconds at which pings will be sent to a peer. Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes. The default ping interval is [code]500[/code] milliseconds.
</description>
</method>
<method name="reset">
@@ -95,7 +95,7 @@
<param index="1" name="packet" type="PackedByteArray" />
<param index="2" name="flags" type="int" />
<description>
- Queues a [code]packet[/code] to be sent over the specified [code]channel[/code]. See [code]FLAG_*[/code] constants for available packet flags.
+ Queues a [param packet] to be sent over the specified [param channel]. See [code]FLAG_*[/code] constants for available packet flags.
</description>
</method>
<method name="set_timeout">
@@ -105,7 +105,7 @@
<param index="2" name="timeout_max" type="int" />
<description>
Sets the timeout parameters for a peer. The timeout parameters control how and when a peer will timeout from a failure to acknowledge reliable traffic. Timeout values are expressed in milliseconds.
- The [code]timeout_limit[/code] is a factor that, multiplied by a value based on the average round trip time, will determine the timeout limit for a reliable packet. When that limit is reached, the timeout will be doubled, and the peer will be disconnected if that limit has reached [code]timeout_min[/code]. The [code]timeout_max[/code] parameter, on the other hand, defines a fixed timeout for which any packet must be acknowledged or the peer will be dropped.
+ The [param timeout] is a factor that, multiplied by a value based on the average round trip time, will determine the timeout limit for a reliable packet. When that limit is reached, the timeout will be doubled, and the peer will be disconnected if that limit has reached [param timeout_min]. The [param timeout_max] parameter, on the other hand, defines a fixed timeout for which any packet must be acknowledged or the peer will be dropped.
</description>
</method>
<method name="throttle_configure">
@@ -115,7 +115,7 @@
<param index="2" name="deceleration" type="int" />
<description>
Configures throttle parameter for a peer.
- Unreliable packets are dropped by ENet in response to the varying conditions of the Internet connection to the peer. The throttle represents a probability that an unreliable packet should not be dropped and thus sent by ENet to the peer. By measuring fluctuations in round trip times of reliable packets over the specified [code]interval[/code], ENet will either increase the probability by the amount specified in the [code]acceleration[/code] parameter, or decrease it by the amount specified in the [code]deceleration[/code] parameter (both are ratios to [constant PACKET_THROTTLE_SCALE]).
+ Unreliable packets are dropped by ENet in response to the varying conditions of the Internet connection to the peer. The throttle represents a probability that an unreliable packet should not be dropped and thus sent by ENet to the peer. By measuring fluctuations in round trip times of reliable packets over the specified [param interval], ENet will either increase the probability by the amount specified in the [param acceleration] parameter, or decrease it by the amount specified in the [param deceleration] parameter (both are ratios to [constant PACKET_THROTTLE_SCALE]).
When the throttle has a value of [constant PACKET_THROTTLE_SCALE], no unreliable packets are dropped by ENet, and so 100% of all unreliable packets will be sent.
When the throttle has a value of [code]0[/code], all unreliable packets are dropped by ENet, and so 0% of all unreliable packets will be sent.
Intermediate values for the throttle represent intermediate probabilities between 0% and 100% of unreliable packets being sent. The bandwidth limits of the local and foreign hosts are taken into account to determine a sensible limit for the throttle probability above which it should not raise even in the best of conditions.
diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub
index 0b86bc569f..421f200f1a 100644
--- a/modules/freetype/SCsub
+++ b/modules/freetype/SCsub
@@ -59,25 +59,7 @@ if env["builtin_freetype"]:
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
if env["brotli"]:
- thirdparty_brotli_dir = "#thirdparty/brotli/"
- thirdparty_brotli_sources = [
- "common/constants.c",
- "common/context.c",
- "common/dictionary.c",
- "common/platform.c",
- "common/shared_dictionary.c",
- "common/transform.c",
- "dec/bit_reader.c",
- "dec/decode.c",
- "dec/huffman.c",
- "dec/state.c",
- ]
- thirdparty_sources += [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
env_freetype.Append(CPPDEFINES=["FT_CONFIG_OPTION_USE_BROTLI"])
- env_freetype.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
-
- if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
- env_freetype.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
if env["platform"] == "uwp":
# Include header for UWP to fix build issues
diff --git a/modules/freetype/config.py b/modules/freetype/config.py
index c0586d5536..d22f9454ed 100644
--- a/modules/freetype/config.py
+++ b/modules/freetype/config.py
@@ -2,13 +2,5 @@ def can_build(env, platform):
return True
-def get_opts(platform):
- from SCons.Variables import BoolVariable
-
- return [
- BoolVariable("brotli", "Enable Brotli decompressor for WOFF2 fonts support", True),
- ]
-
-
def configure(env):
pass
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 1234abc62e..d8f12f7232 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -609,7 +609,7 @@
<param index="3" name="transfer_channel" type="int" default="0" />
<description>
Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url].
- The order of [code]mode[/code], [code]sync[/code] and [code]transfer_mode[/code] does not matter and all arguments can be omitted, but [code]transfer_channel[/code] always has to be the last argument. The accepted values for [code]mode[/code] are [code]"any_peer"[/code] or [code]"authority"[/code], for [code]sync[/code] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [code]transfer_mode[/code] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code].
+ The order of [param mode], [param sync] and [param transfer_mode] does not matter and all arguments can be omitted, but [param transfer_channel] always has to be the last argument. The accepted values for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code], for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code].
[codeblock]
@rpc
func fn(): pass
@@ -622,6 +622,12 @@
[/codeblock]
</description>
</annotation>
+ <annotation name="@static_unload">
+ <return type="void" />
+ <description>
+ Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values.
+ </description>
+ </annotation>
<annotation name="@tool">
<return type="void" />
<description>
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
new file mode 100644
index 0000000000..451af996ec
--- /dev/null
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -0,0 +1,271 @@
+/**************************************************************************/
+/* gdscript_docgen.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 "gdscript_docgen.h"
+#include "../gdscript.h"
+
+using GDP = GDScriptParser;
+using GDType = GDP::DataType;
+
+static String _get_script_path(const String &p_path) {
+ return vformat(R"("%s")", p_path.get_slice("://", 1));
+}
+
+static String _get_class_name(const GDP::ClassNode &p_class) {
+ const GDP::ClassNode *curr_class = &p_class;
+ if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ return _get_script_path(curr_class->fqcn);
+ }
+
+ String full_name = curr_class->identifier->name;
+ while (curr_class->outer) {
+ curr_class = curr_class->outer;
+ if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name);
+ }
+ full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
+ }
+ return full_name;
+}
+
+static PropertyInfo _property_info_from_datatype(const GDType &p_type) {
+ PropertyInfo pi;
+ pi.type = p_type.builtin_type;
+ if (p_type.kind == GDType::CLASS) {
+ pi.class_name = _get_class_name(*p_type.class_type);
+ } else if (p_type.kind == GDType::ENUM && p_type.enum_type != StringName()) {
+ pi.type = Variant::INT; // Only int types are recognized as enums by the EditorHelp
+ pi.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ // Replace :: from enum's use of fully qualified class names with regular .
+ pi.class_name = String(p_type.native_type).replace("::", ".");
+ } else if (p_type.kind == GDType::NATIVE) {
+ pi.class_name = p_type.native_type;
+ }
+ return pi;
+}
+
+void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
+ p_script->_clear_doc();
+
+ DocData::ClassDoc &doc = p_script->doc;
+
+ doc.script_path = _get_script_path(p_script->get_script_path());
+ if (p_script->name.is_empty()) {
+ doc.name = doc.script_path;
+ } else {
+ doc.name = p_script->name;
+ }
+
+ if (p_script->_owner) {
+ doc.name = p_script->_owner->doc.name + "." + doc.name;
+ doc.script_path = doc.script_path + "." + doc.name;
+ }
+
+ doc.is_script_doc = true;
+
+ if (p_script->base.is_valid() && p_script->base->is_valid()) {
+ if (!p_script->base->doc.name.is_empty()) {
+ doc.inherits = p_script->base->doc.name;
+ } else {
+ doc.inherits = p_script->base->get_instance_base_type();
+ }
+ } else if (p_script->native.is_valid()) {
+ doc.inherits = p_script->native->get_name();
+ }
+
+ doc.brief_description = p_class->doc_brief_description;
+ doc.description = p_class->doc_description;
+ for (const Pair<String, String> &p : p_class->doc_tutorials) {
+ DocData::TutorialDoc td;
+ td.title = p.first;
+ td.link = p.second;
+ doc.tutorials.append(td);
+ }
+
+ for (const GDP::ClassNode::Member &member : p_class->members) {
+ switch (member.type) {
+ case GDP::ClassNode::Member::CLASS: {
+ const GDP::ClassNode *inner_class = member.m_class;
+ const StringName &class_name = inner_class->identifier->name;
+
+ p_script->member_lines[class_name] = inner_class->start_line;
+
+ // Recursively generate inner class docs
+ // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts()
+ GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class);
+ } break;
+
+ case GDP::ClassNode::Member::CONSTANT: {
+ const GDP::ConstantNode *m_const = member.constant;
+ const StringName &const_name = member.constant->identifier->name;
+
+ p_script->member_lines[const_name] = m_const->start_line;
+
+ DocData::ConstantDoc const_doc;
+ DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_description);
+ doc.constants.push_back(const_doc);
+ } break;
+
+ case GDP::ClassNode::Member::FUNCTION: {
+ const GDP::FunctionNode *m_func = member.function;
+ const StringName &func_name = m_func->identifier->name;
+
+ p_script->member_lines[func_name] = m_func->start_line;
+
+ MethodInfo mi;
+ mi.name = func_name;
+
+ if (m_func->return_type) {
+ mi.return_val = _property_info_from_datatype(m_func->return_type->get_datatype());
+ }
+ for (const GDScriptParser::ParameterNode *p : m_func->parameters) {
+ PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
+ pi.name = p->identifier->name;
+ mi.arguments.push_back(pi);
+ }
+
+ DocData::MethodDoc method_doc;
+ DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_description);
+ doc.methods.push_back(method_doc);
+ } break;
+
+ case GDP::ClassNode::Member::SIGNAL: {
+ const GDP::SignalNode *m_signal = member.signal;
+ const StringName &signal_name = m_signal->identifier->name;
+
+ p_script->member_lines[signal_name] = m_signal->start_line;
+
+ MethodInfo mi;
+ mi.name = signal_name;
+ for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
+ PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
+ pi.name = p->identifier->name;
+ mi.arguments.push_back(pi);
+ }
+
+ DocData::MethodDoc signal_doc;
+ DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_description);
+ doc.signals.push_back(signal_doc);
+ } break;
+
+ case GDP::ClassNode::Member::VARIABLE: {
+ const GDP::VariableNode *m_var = member.variable;
+ const StringName &var_name = m_var->identifier->name;
+
+ p_script->member_lines[var_name] = m_var->start_line;
+
+ DocData::PropertyDoc prop_doc;
+
+ prop_doc.name = var_name;
+ prop_doc.description = m_var->doc_description;
+
+ GDType dt = m_var->get_datatype();
+ switch (dt.kind) {
+ case GDType::CLASS:
+ prop_doc.type = _get_class_name(*dt.class_type);
+ break;
+ case GDType::VARIANT:
+ prop_doc.type = "Variant";
+ break;
+ case GDType::ENUM:
+ prop_doc.type = Variant::get_type_name(dt.builtin_type);
+ // Replace :: from enum's use of fully qualified class names with regular .
+ prop_doc.enumeration = String(dt.native_type).replace("::", ".");
+ break;
+ case GDType::NATIVE:;
+ prop_doc.type = dt.native_type;
+ break;
+ case GDType::BUILTIN:
+ prop_doc.type = Variant::get_type_name(dt.builtin_type);
+ break;
+ default:
+ // SCRIPT: can be preload()'d and perhaps used as types directly?
+ // RESOLVING & UNRESOLVED should never happen since docgen requires analyzing w/o errors
+ break;
+ }
+
+ if (m_var->property == GDP::VariableNode::PROP_SETGET) {
+ if (m_var->setter_pointer != nullptr) {
+ prop_doc.setter = m_var->setter_pointer->name;
+ }
+ if (m_var->getter_pointer != nullptr) {
+ prop_doc.getter = m_var->getter_pointer->name;
+ }
+ }
+
+ if (m_var->initializer && m_var->initializer->is_constant) {
+ prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "");
+ }
+
+ prop_doc.overridden = false;
+
+ doc.properties.push_back(prop_doc);
+ } break;
+
+ case GDP::ClassNode::Member::ENUM: {
+ const GDP::EnumNode *m_enum = member.m_enum;
+ StringName name = m_enum->identifier->name;
+
+ p_script->member_lines[name] = m_enum->start_line;
+
+ for (const GDP::EnumNode::Value &val : m_enum->values) {
+ DocData::ConstantDoc const_doc;
+ const_doc.name = val.identifier->name;
+ const_doc.value = String(Variant(val.value));
+ const_doc.description = val.doc_description;
+ const_doc.enumeration = name;
+
+ doc.enums[const_doc.name] = const_doc.description;
+ doc.constants.push_back(const_doc);
+ }
+
+ } break;
+
+ case GDP::ClassNode::Member::ENUM_VALUE: {
+ const GDP::EnumNode::Value &m_enum_val = member.enum_value;
+ const StringName &name = m_enum_val.identifier->name;
+
+ p_script->member_lines[name] = m_enum_val.identifier->start_line;
+
+ DocData::ConstantDoc constant_doc;
+ constant_doc.enumeration = "@unnamed_enums";
+ DocData::constant_doc_from_variant(constant_doc, name, m_enum_val.value, m_enum_val.doc_description);
+ doc.constants.push_back(constant_doc);
+ } break;
+ case GDP::ClassNode::Member::GROUP:
+ case GDP::ClassNode::Member::UNDEFINED:
+ default:
+ break;
+ }
+ }
+
+ // Add doc to the outer-most class.
+ p_script->_add_doc(doc);
+}
diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h
new file mode 100644
index 0000000000..bb3647196a
--- /dev/null
+++ b/modules/gdscript/editor/gdscript_docgen.h
@@ -0,0 +1,42 @@
+/**************************************************************************/
+/* gdscript_docgen.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 GDSCRIPT_DOCGEN_H
+#define GDSCRIPT_DOCGEN_H
+
+#include "../gdscript_parser.h"
+#include "core/doc_data.h"
+
+class GDScriptDocGen {
+public:
+ static void generate_docs(GDScript *p_script, const GDScriptParser::ClassNode *p_class);
+};
+
+#endif // GDSCRIPT_DOCGEN_H
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 2646c1ad15..3bc11c69e9 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -52,6 +52,7 @@
#ifdef TOOLS_ENABLED
#include "editor/editor_paths.h"
+#include "editor/gdscript_docgen.h"
#endif
///////////////////////////
@@ -340,12 +341,11 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl
r_list->push_back(E);
}
- props.clear();
-
if (!p_include_base) {
break;
}
+ props.clear();
sptr = sptr->_base;
}
}
@@ -461,9 +461,9 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List
}
void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) {
- if (_owner) {
+ if (_owner) { // Only the top-level class stores doc info
_owner->_add_doc(p_inner_class);
- } else {
+ } else { // Remove old docs, add new
for (int i = 0; i < docs.size(); i++) {
if (docs[i].name == p_inner_class.name) {
docs.remove_at(i);
@@ -478,167 +478,6 @@ void GDScript::_clear_doc() {
docs.clear();
doc = DocData::ClassDoc();
}
-
-void GDScript::_update_doc() {
- _clear_doc();
-
- doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1));
- if (!name.is_empty()) {
- doc.name = name;
- } else {
- doc.name = doc.script_path;
- }
-
- if (_owner) {
- doc.name = _owner->doc.name + "." + doc.name;
- doc.script_path = doc.script_path + "." + doc.name;
- }
-
- doc.is_script_doc = true;
-
- if (base.is_valid() && base->is_valid()) {
- if (!base->doc.name.is_empty()) {
- doc.inherits = base->doc.name;
- } else {
- doc.inherits = base->get_instance_base_type();
- }
- } else if (native.is_valid()) {
- doc.inherits = native->get_name();
- }
-
- doc.brief_description = doc_brief_description;
- doc.description = doc_description;
- doc.tutorials = doc_tutorials;
-
- for (const KeyValue<String, DocData::EnumDoc> &E : doc_enums) {
- if (!E.value.description.is_empty()) {
- doc.enums[E.key] = E.value.description;
- }
- }
-
- List<MethodInfo> methods;
- _get_script_method_list(&methods, false);
- for (int i = 0; i < methods.size(); i++) {
- // Ignore internal methods.
- if (methods[i].name[0] == '@') {
- continue;
- }
-
- DocData::MethodDoc method_doc;
- const String &class_name = methods[i].name;
- if (member_functions.has(class_name)) {
- GDScriptFunction *fn = member_functions[class_name];
-
- // Change class name if return type is script reference.
- GDScriptDataType return_type = fn->get_return_type();
- if (return_type.kind == GDScriptDataType::GDSCRIPT) {
- methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type));
- }
-
- // Change class name if argument is script reference.
- for (int j = 0; j < fn->get_argument_count(); j++) {
- GDScriptDataType arg_type = fn->get_argument_type(j);
- if (arg_type.kind == GDScriptDataType::GDSCRIPT) {
- methods[i].arguments[j].class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(arg_type.script_type));
- }
- }
- }
- if (doc_functions.has(methods[i].name)) {
- DocData::method_doc_from_methodinfo(method_doc, methods[i], doc_functions[methods[i].name]);
- } else {
- DocData::method_doc_from_methodinfo(method_doc, methods[i], String());
- }
- doc.methods.push_back(method_doc);
- }
-
- List<PropertyInfo> props;
- _get_script_property_list(&props, false);
- for (int i = 0; i < props.size(); i++) {
- if (props[i].usage & PROPERTY_USAGE_CATEGORY || props[i].usage & PROPERTY_USAGE_GROUP || props[i].usage & PROPERTY_USAGE_SUBGROUP) {
- continue;
- }
- ScriptMemberInfo scr_member_info;
- scr_member_info.propinfo = props[i];
- scr_member_info.propinfo.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- if (member_indices.has(props[i].name)) {
- const MemberInfo &mi = member_indices[props[i].name];
- scr_member_info.setter = mi.setter;
- scr_member_info.getter = mi.getter;
- if (mi.data_type.kind == GDScriptDataType::GDSCRIPT) {
- scr_member_info.propinfo.class_name = _get_gdscript_reference_class_name(
- Object::cast_to<GDScript>(mi.data_type.script_type));
- }
- }
- if (member_default_values.has(props[i].name)) {
- scr_member_info.has_default_value = true;
- scr_member_info.default_value = member_default_values[props[i].name];
- }
- if (doc_variables.has(props[i].name)) {
- scr_member_info.doc_string = doc_variables[props[i].name];
- }
-
- DocData::PropertyDoc prop_doc;
- DocData::property_doc_from_scriptmemberinfo(prop_doc, scr_member_info);
- doc.properties.push_back(prop_doc);
- }
-
- List<MethodInfo> signals;
- _get_script_signal_list(&signals, false);
- for (int i = 0; i < signals.size(); i++) {
- DocData::MethodDoc signal_doc;
- if (doc_signals.has(signals[i].name)) {
- DocData::signal_doc_from_methodinfo(signal_doc, signals[i], doc_signals[signals[i].name]);
- } else {
- DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String());
- }
- doc.signals.push_back(signal_doc);
- }
-
- for (const KeyValue<StringName, Variant> &E : constants) {
- if (subclasses.has(E.key)) {
- continue;
- }
-
- // Enums.
- bool is_enum = false;
- if (E.value.get_type() == Variant::DICTIONARY) {
- if (doc_enums.has(E.key)) {
- is_enum = true;
- for (int i = 0; i < doc_enums[E.key].values.size(); i++) {
- doc_enums[E.key].values.write[i].enumeration = E.key;
- doc.constants.push_back(doc_enums[E.key].values[i]);
- }
- }
- }
- if (!is_enum && doc_enums.has("@unnamed_enums")) {
- for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) {
- if (E.key == doc_enums["@unnamed_enums"].values[i].name) {
- is_enum = true;
- DocData::ConstantDoc constant_doc;
- constant_doc.enumeration = "@unnamed_enums";
- DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_enums["@unnamed_enums"].values[i].description);
- doc.constants.push_back(constant_doc);
- break;
- }
- }
- }
- if (!is_enum) {
- DocData::ConstantDoc constant_doc;
- String const_description;
- if (doc_constants.has(E.key)) {
- const_description = doc_constants[E.key];
- }
- DocData::constant_doc_from_variant(constant_doc, E.key, E.value, const_description);
- doc.constants.push_back(constant_doc);
- }
- }
-
- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
- E.value->_update_doc();
- }
-
- _add_doc(doc);
-}
#endif
bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) {
@@ -812,6 +651,49 @@ String GDScript::_get_debug_path() const {
}
}
+Error GDScript::_static_init() {
+ if (static_initializer) {
+ Callable::CallError call_err;
+ static_initializer->call(nullptr, nullptr, 0, call_err);
+ if (call_err.error != Callable::CallError::CALL_OK) {
+ return ERR_CANT_CREATE;
+ }
+ }
+ Error err = OK;
+ for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+ err = inner.value->_static_init();
+ if (err) {
+ break;
+ }
+ }
+ return err;
+}
+
+#ifdef TOOLS_ENABLED
+
+void GDScript::_save_old_static_data() {
+ old_static_variables_indices = static_variables_indices;
+ old_static_variables = static_variables;
+ for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+ inner.value->_save_old_static_data();
+ }
+}
+
+void GDScript::_restore_old_static_data() {
+ for (KeyValue<StringName, MemberInfo> &E : old_static_variables_indices) {
+ if (static_variables_indices.has(E.key)) {
+ static_variables.write[static_variables_indices[E.key].index] = old_static_variables[E.value.index];
+ }
+ }
+ old_static_variables_indices.clear();
+ old_static_variables.clear();
+ for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+ inner.value->_restore_old_static_data();
+ }
+}
+
+#endif
+
Error GDScript::reload(bool p_keep_state) {
if (reloading) {
return OK;
@@ -857,6 +739,14 @@ Error GDScript::reload(bool p_keep_state) {
}
}
+ bool can_run = ScriptServer::is_scripting_enabled() || is_tool();
+
+#ifdef DEBUG_ENABLED
+ if (p_keep_state && can_run && is_valid()) {
+ _save_old_static_data();
+ }
+#endif
+
valid = false;
GDScriptParser parser;
Error err = parser.parse(source, path, false);
@@ -887,7 +777,7 @@ Error GDScript::reload(bool p_keep_state) {
return ERR_PARSE_ERROR;
}
- bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
+ can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
@@ -905,6 +795,13 @@ Error GDScript::reload(bool p_keep_state) {
return err;
}
}
+
+#ifdef TOOLS_ENABLED
+ // Done after compilation because it needs the GDScript object's inner class GDScript objects,
+ // which are made by calling make_scripts() within compiler.compile() above.
+ GDScriptDocGen::generate_docs(this, parser.get_tree());
+#endif
+
#ifdef DEBUG_ENABLED
for (const GDScriptWarning &warning : parser.get_warnings()) {
if (EngineDebugger::is_active()) {
@@ -914,6 +811,19 @@ Error GDScript::reload(bool p_keep_state) {
}
#endif
+ if (can_run) {
+ err = _static_init();
+ if (err) {
+ return err;
+ }
+ }
+
+#ifdef DEBUG_ENABLED
+ if (can_run && p_keep_state) {
+ _restore_old_static_data();
+ }
+#endif
+
reloading = false;
return OK;
}
@@ -942,6 +852,10 @@ const Variant GDScript::get_rpc_config() const {
return rpc_config;
}
+void GDScript::unload_static() const {
+ GDScriptCache::remove_script(fully_qualified_name);
+}
+
Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
GDScript *top = this;
while (top) {
@@ -978,6 +892,19 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
}
+
+ {
+ HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
+ if (E) {
+ if (E->value.getter) {
+ Callable::CallError ce;
+ r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ return true;
+ }
+ r_ret = static_variables[E->value.index];
+ return true;
+ }
+ }
top = top->_base;
}
@@ -995,7 +922,32 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
set_source_code(p_value);
reload();
} else {
- return false;
+ const GDScript *top = this;
+ while (top) {
+ HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
+ if (E) {
+ const GDScript::MemberInfo *member = &E->value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
+ Callable::CallError err;
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
+ return false;
+ }
+ }
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
+ } else {
+ static_variables.write[member->index] = value;
+ return true;
+ }
+ }
+ top = top->_base;
+ }
}
return true;
@@ -1266,7 +1218,6 @@ void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_
else if (base_cache.is_valid()) {
base_cache->get_script_signal_list(r_list);
}
-
#endif
}
@@ -1448,6 +1399,13 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
E.value.data_type.script_type_ref = Ref<Script>();
}
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) {
+ clear_data->scripts.insert(E.value.data_type.script_type_ref);
+ E.value.data_type.script_type_ref = Ref<Script>();
+ }
+ static_variables.clear();
+ static_variables_indices.clear();
+
if (implicit_initializer) {
clear_data->functions.insert(implicit_initializer);
implicit_initializer = nullptr;
@@ -1458,6 +1416,11 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
implicit_ready = nullptr;
}
+ if (static_initializer) {
+ clear_data->functions.insert(static_initializer);
+ static_initializer = nullptr;
+ }
+
_save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
@@ -1512,10 +1475,6 @@ GDScript::~GDScript() {
GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
}
-
- if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
- GDScriptCache::remove_script(get_path());
- }
}
//////////////////////////////
@@ -2546,6 +2505,7 @@ GDScriptLanguage::GDScriptLanguage() {
ERR_FAIL_COND(singleton);
singleton = this;
strings._init = StaticCString::create("_init");
+ strings._static_init = StaticCString::create("_static_init");
strings._notification = StaticCString::create("_notification");
strings._set = StaticCString::create("_set");
strings._get = StaticCString::create("_get");
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 0117ed40ab..60bd9eef53 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -83,6 +83,7 @@ class GDScript : public Script {
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
+ friend class GDScriptDocGen;
friend class GDScriptLanguage;
friend struct GDScriptUtilityFunctionsDefinitions;
@@ -93,6 +94,8 @@ class GDScript : public Script {
HashSet<StringName> members; //members are just indices to the instantiated script.
HashMap<StringName, Variant> constants;
+ HashMap<StringName, MemberInfo> static_variables_indices;
+ Vector<Variant> static_variables;
HashMap<StringName, GDScriptFunction *> member_functions;
HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
HashMap<StringName, Ref<GDScript>> subclasses;
@@ -101,6 +104,12 @@ class GDScript : public Script {
#ifdef TOOLS_ENABLED
+ // For static data storage during hot-reloading.
+ HashMap<StringName, MemberInfo> old_static_variables_indices;
+ Vector<Variant> old_static_variables;
+ void _save_old_static_data();
+ void _restore_old_static_data();
+
HashMap<StringName, int> member_lines;
HashMap<StringName, Variant> member_default_values;
List<PropertyInfo> members_cache;
@@ -113,16 +122,7 @@ class GDScript : public Script {
DocData::ClassDoc doc;
Vector<DocData::ClassDoc> docs;
- String doc_brief_description;
- String doc_description;
- Vector<DocData::TutorialDoc> doc_tutorials;
- HashMap<String, String> doc_functions;
- HashMap<String, String> doc_variables;
- HashMap<String, String> doc_constants;
- HashMap<String, String> doc_signals;
- HashMap<String, DocData::EnumDoc> doc_enums;
void _clear_doc();
- void _update_doc();
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif
@@ -131,6 +131,9 @@ class GDScript : public Script {
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
GDScriptFunction *implicit_ready = nullptr;
+ GDScriptFunction *static_initializer = nullptr;
+
+ Error _static_init();
int subclass_count = 0;
RBSet<Object *> instances;
@@ -276,6 +279,8 @@ public:
virtual const Variant get_rpc_config() const override;
+ void unload_static() const;
+
#ifdef TOOLS_ENABLED
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
#endif
@@ -447,6 +452,7 @@ public:
struct {
StringName _init;
+ StringName _static_init;
StringName _notification;
StringName _set;
StringName _get;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index aa91f8fe8d..46edc0431d 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -879,6 +879,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
#endif
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
+ bool previous_static_context = static_context;
+ static_context = member.variable->is_static;
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
member.variable->set_datatype(resolving_datatype);
resolve_variable(member.variable, false);
@@ -890,6 +892,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
E->apply(parser, member.variable);
}
}
+ static_context = previous_static_context;
#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
@@ -897,7 +900,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
if (member.variable->initializer) {
// Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed.
// This could be improved by traversing the expression fully and checking the presence of get_node at any level.
- if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
+ if (!member.variable->is_static && !member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
GDScriptParser::Node *expr = member.variable->initializer;
if (expr->type == GDScriptParser::Node::CAST) {
expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
@@ -1082,6 +1085,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
p_source = p_class;
}
+#ifdef DEBUG_ENABLED
+ bool has_static_data = p_class->has_static_data;
+#endif
+
if (!p_class->resolved_interface) {
if (!parser->has_class(p_class)) {
String script_path = p_class->get_datatype().script_path;
@@ -1124,7 +1131,29 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
for (int i = 0; i < p_class->members.size(); i++) {
resolve_class_member(p_class, i);
+
+#ifdef DEBUG_ENABLED
+ if (!has_static_data) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+ if (member.type == GDScriptParser::ClassNode::Member::CLASS) {
+ has_static_data = member.m_class->has_static_data;
+ }
+ }
+#endif
+ }
+
+#ifdef DEBUG_ENABLED
+ if (!has_static_data && p_class->annotated_static_unload) {
+ GDScriptParser::Node *static_unload = nullptr;
+ for (GDScriptParser::AnnotationNode *node : p_class->annotations) {
+ if (node->name == "@static_unload") {
+ static_unload = node;
+ break;
+ }
+ }
+ parser->push_warning(static_unload ? static_unload : p_class, GDScriptWarning::REDUNDANT_STATIC_UNLOAD);
}
+#endif
}
}
@@ -1499,6 +1528,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
GDScriptParser::FunctionNode *previous_function = parser->current_function;
parser->current_function = p_function;
+ bool previous_static_context = static_context;
+ static_context = p_function->is_static;
GDScriptParser::DataType prev_datatype = p_function->get_datatype();
@@ -1542,6 +1573,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
push_error("Constructor cannot have an explicit return type.", p_function->return_type);
}
}
+ } else if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._static_init) {
+ // Static constructor.
+ GDScriptParser::DataType return_type;
+ return_type.kind = GDScriptParser::DataType::BUILTIN;
+ return_type.builtin_type = Variant::NIL;
+ p_function->set_datatype(return_type);
+ if (p_function->return_type) {
+ GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type);
+ if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) {
+ push_error("Static constructor cannot have an explicit return type.", p_function->return_type);
+ }
+ }
} else {
if (p_function->return_type != nullptr) {
p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
@@ -1625,6 +1668,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
parser->ignored_warnings = previously_ignored_warnings;
#endif
parser->current_function = previous_function;
+ static_context = previous_static_context;
}
void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) {
@@ -3050,13 +3094,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
base_type.is_meta_type = false;
}
- if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
- // Get the parent function above any lambda.
- GDScriptParser::FunctionNode *parent_function = parser->current_function;
- while (parent_function->source_lambda) {
- parent_function = parent_function->source_lambda->parent_function;
+ if (is_self && static_context && !is_static) {
+ if (parser->current_function) {
+ // Get the parent function above any lambda.
+ GDScriptParser::FunctionNode *parent_function = parser->current_function;
+ while (parent_function->source_lambda) {
+ parent_function = parent_function->source_lambda->parent_function;
+ }
+ push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
+ } else {
+ push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call);
}
- push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
} else if (!is_self && base_type.is_meta_type && !is_static) {
base_type.is_meta_type = false; // For `to_string()`.
push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
@@ -3073,7 +3121,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
}
- if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) {
+ if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) {
String caller_type = String(base_type.native_type);
if (caller_type.is_empty()) {
@@ -3428,9 +3476,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
case GDScriptParser::ClassNode::Member::VARIABLE: {
- if (is_base && !base.is_meta_type) {
+ if (is_base && (!base.is_meta_type || member.variable->is_static)) {
p_identifier->set_datatype(member.get_datatype());
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+ p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
p_identifier->variable_source = member.variable;
member.variable->usages += 1;
return;
@@ -3572,6 +3620,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
mark_lambda_use_self();
p_identifier->variable_source->usages++;
[[fallthrough]];
+ case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
found_source = true;
@@ -3602,13 +3651,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (found_source) {
bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
- if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) {
- // Get the parent function above any lambda.
- GDScriptParser::FunctionNode *parent_function = parser->current_function;
- while (parent_function->source_lambda) {
- parent_function = parent_function->source_lambda->parent_function;
+ if ((source_is_variable || source_is_signal) && static_context) {
+ if (parser->current_function) {
+ // Get the parent function above any lambda.
+ GDScriptParser::FunctionNode *parent_function = parser->current_function;
+ while (parent_function->source_lambda) {
+ parent_function = parent_function->source_lambda->parent_function;
+ }
+ push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
+ } else {
+ push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier);
}
- push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
}
if (!lambda_stack.is_empty()) {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 5902035bcd..0c7bf4125b 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -43,6 +43,7 @@ class GDScriptAnalyzer {
const GDScriptParser::EnumNode *current_enum = nullptr;
List<GDScriptParser::LambdaNode *> lambda_stack;
+ bool static_context = false;
// Tests for detecting invalid overloading of script members
static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member);
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 42c6f80455..fc684e4d8f 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -366,6 +366,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
case Address::CONSTANT:
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
+ case Address::STATIC_VARIABLE:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS);
case Address::LOCAL_VARIABLE:
case Address::FUNCTION_PARAMETER:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index a009e8e0a8..126fccbbf0 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -342,6 +342,16 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
return err;
}
+void GDScriptCache::add_static_script(Ref<GDScript> p_script) {
+ ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static.");
+ ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static.");
+ singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script;
+}
+
+void GDScriptCache::remove_static_script(const String &p_fqcn) {
+ singleton->static_gdscript_cache.erase(p_fqcn);
+}
+
Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index c7f40f6e82..28266a1c0b 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -78,6 +78,7 @@ class GDScriptCache {
HashMap<String, GDScriptParserRef *> parser_map;
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
HashMap<String, Ref<GDScript>> full_gdscript_cache;
+ HashMap<String, Ref<GDScript>> static_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
HashMap<String, Ref<PackedScene>> packed_scene_cache;
HashMap<String, HashSet<String>> packed_scene_dependencies;
@@ -101,6 +102,8 @@ public:
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
static Ref<GDScript> get_cached_script(const String &p_path);
static Error finish_compiling(const String &p_owner);
+ static void add_static_script(Ref<GDScript> p_script);
+ static void remove_static_script(const String &p_fqcn);
static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
static void clear_unreferenced_packed_scenes();
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index e82b4b08ab..dbc2466393 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -44,6 +44,7 @@ public:
CLASS,
MEMBER,
CONSTANT,
+ STATIC_VARIABLE,
LOCAL_VARIABLE,
FUNCTION_PARAMETER,
TEMPORARY,
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 9e5c83d08c..327e24ef11 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -254,13 +254,29 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
return temp;
} else {
- // No getter or inside getter: direct member access.,
+ // No getter or inside getter: direct member access.
int idx = codegen.script->member_indices[identifier].index;
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
}
}
}
+ // Try static variables.
+ if (codegen.script->static_variables_indices.has(identifier)) {
+ if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args);
+ return temp;
+ } else {
+ // No getter or inside getter: direct variable access.
+ int idx = codegen.script->static_variables_indices[identifier].index;
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type);
+ }
+ }
+
// Try class constants.
{
GDScript *owner = codegen.script;
@@ -563,7 +579,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Not exact arguments, but still can use method bind call.
gen->write_call_method_bind(result, self, method, arguments);
}
- } else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
+ } else if (codegen.is_static || (codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
GDScriptCodeGenerator::Address self;
self.mode = GDScriptCodeGenerator::Address::CLASS;
if (is_awaited) {
@@ -909,6 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool is_member_property = false;
bool member_property_has_setter = false;
bool member_property_is_in_setter = false;
+ bool is_static = false;
StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@@ -925,14 +942,16 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName var_name = identifier->name;
if (_is_class_member_property(codegen, var_name)) {
assign_class_member_property = var_name;
- } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) {
+ } else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
is_member_property = true;
- member_property_setter_function = codegen.script->member_indices[var_name].setter;
+ is_static = codegen.script->static_variables_indices.has(var_name);
+ const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
+ member_property_setter_function = minfo.setter;
member_property_has_setter = member_property_setter_function != StringName();
member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
- target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
- target_member_property.address = codegen.script->member_indices[var_name].index;
- target_member_property.type = codegen.script->member_indices[var_name].data_type;
+ target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
+ target_member_property.address = minfo.index;
+ target_member_property.type = minfo.data_type;
}
}
break;
@@ -1085,7 +1104,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (member_property_has_setter && !member_property_is_in_setter) {
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(assigned);
- gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
+ GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
+ gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args);
} else {
gen->write_assign(target_member_property, assigned);
}
@@ -1134,16 +1154,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool is_member = false;
bool has_setter = false;
bool is_in_setter = false;
+ bool is_static = false;
StringName setter_function;
StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) {
+ if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
is_member = true;
- setter_function = codegen.script->member_indices[var_name].setter;
+ is_static = codegen.script->static_variables_indices.has(var_name);
+ GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
+ setter_function = minfo.setter;
has_setter = setter_function != StringName();
is_in_setter = has_setter && setter_function == codegen.function_name;
- member.mode = GDScriptCodeGenerator::Address::MEMBER;
- member.address = codegen.script->member_indices[var_name].index;
- member.type = codegen.script->member_indices[var_name].data_type;
+ member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
+ member.address = minfo.index;
+ member.type = minfo.data_type;
}
GDScriptCodeGenerator::Address target;
@@ -1165,18 +1188,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
if (has_operation) {
// Perform operation.
- GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
-
- if (!has_setter && !assignment->use_conversion_assign) {
- // If there's nothing special about the assignment, perform the assignment as part of the operator
- gen->write_binary_operator(target, assignment->variant_op, og_value, assigned_value);
- if (assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary(); // Pop assigned value if not done before.
- }
- return GDScriptCodeGenerator::Address();
- }
-
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
+ GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value);
to_assign = op_result;
@@ -2011,6 +2024,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
codegen.function_name = func_name;
+ codegen.is_static = is_static;
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
int optional_parameters = 0;
@@ -2034,7 +2048,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
bool is_implicit_ready = !p_func && p_for_ready;
if (!p_for_lambda && is_implicit_initializer) {
- // Initialize the default values for type variables before anything.
+ // Initialize the default values for typed variables before anything.
// This avoids crashes if they are accessed with validated calls before being properly initialized.
// It may happen with out-of-order access or with `@onready` variables.
for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
@@ -2043,6 +2057,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
const GDScriptParser::VariableNode *field = member.variable;
+ if (field->is_static) {
+ continue;
+ }
+
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
@@ -2066,6 +2084,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+ if (field->is_static) {
+ continue;
+ }
+
if (field->onready != is_implicit_ready) {
// Only initialize in @implicit_ready.
continue;
@@ -2155,12 +2177,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (p_func) {
codegen.generator->set_initial_line(p_func->start_line);
-#ifdef TOOLS_ENABLED
- if (!p_for_lambda) {
- p_script->member_lines[func_name] = p_func->start_line;
- p_script->doc_functions[func_name] = p_func->doc_description;
- }
-#endif
} else {
codegen.generator->set_initial_line(0);
}
@@ -2199,6 +2215,135 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return gd_function;
}
+GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class) {
+ r_error = OK;
+ CodeGen codegen;
+ codegen.generator = memnew(GDScriptByteCodeGenerator);
+
+ codegen.class_node = p_class;
+ codegen.script = p_script;
+
+ StringName func_name = SNAME("@static_initializer");
+ bool is_static = true;
+ Variant rpc_config;
+ GDScriptDataType return_type;
+ return_type.has_type = true;
+ return_type.kind = GDScriptDataType::BUILTIN;
+ return_type.builtin_type = Variant::NIL;
+
+ codegen.function_name = func_name;
+ codegen.is_static = is_static;
+ codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
+
+ GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+
+ // Initialize the default values for typed variables before anything.
+ // This avoids crashes if they are accessed with validated calls before being properly initialized.
+ // It may happen with out-of-order access or with `@onready` variables.
+ for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
+ if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+
+ const GDScriptParser::VariableNode *field = member.variable;
+ if (!field->is_static) {
+ continue;
+ }
+
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+
+ if (field_type.has_type) {
+ codegen.generator->write_newline(field->start_line);
+
+ if (field_type.has_container_element_type()) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->pop_temporary();
+
+ } else if (field_type.kind == GDScriptDataType::BUILTIN) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->pop_temporary();
+ }
+ // The `else` branch is for objects, in such case we leave it as `null`.
+ }
+ }
+
+ for (int i = 0; i < p_class->members.size(); i++) {
+ // Initialize static fields.
+ if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+ const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+ if (!field->is_static) {
+ continue;
+ }
+
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+
+ if (field->initializer) {
+ // Emit proper line change.
+ codegen.generator->write_newline(field->initializer->start_line);
+
+ GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true);
+ if (r_error) {
+ memdelete(codegen.generator);
+ return nullptr;
+ }
+
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ if (field->use_conversion_assign) {
+ codegen.generator->write_assign_with_conversion(temp, src_address);
+ } else {
+ codegen.generator->write_assign(temp, src_address);
+ }
+ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+
+ codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->pop_temporary();
+ }
+ }
+
+ if (p_script->has_method(GDScriptLanguage::get_singleton()->strings._static_init)) {
+ codegen.generator->write_newline(p_class->start_line);
+ codegen.generator->write_call(GDScriptCodeGenerator::Address(), class_addr, GDScriptLanguage::get_singleton()->strings._static_init, Vector<GDScriptCodeGenerator::Address>());
+ }
+
+#ifdef DEBUG_ENABLED
+ if (EngineDebugger::is_active()) {
+ String signature;
+ // Path.
+ if (!p_script->get_script_path().is_empty()) {
+ signature += p_script->get_script_path();
+ }
+ // Location.
+ signature += "::0";
+
+ // Function and class.
+
+ if (p_class->identifier) {
+ signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
+ } else {
+ signature += "::" + String(func_name);
+ }
+
+ codegen.generator->set_signature(signature);
+ }
+#endif
+
+ codegen.generator->set_initial_line(p_class->start_line);
+
+ GDScriptFunction *gd_function = codegen.generator->write_end();
+
+ memdelete(codegen.generator);
+
+ return gd_function;
+}
+
Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
Error err = OK;
@@ -2229,23 +2374,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
parsing_classes.insert(p_script);
p_script->clearing = true;
-#ifdef TOOLS_ENABLED
- p_script->doc_functions.clear();
- p_script->doc_variables.clear();
- p_script->doc_constants.clear();
- p_script->doc_enums.clear();
- p_script->doc_signals.clear();
- p_script->doc_tutorials.clear();
-
- p_script->doc_brief_description = p_class->doc_brief_description;
- p_script->doc_description = p_class->doc_description;
- for (int i = 0; i < p_class->doc_tutorials.size(); i++) {
- DocData::TutorialDoc td;
- td.title = p_class->doc_tutorials[i].first;
- td.link = p_class->doc_tutorials[i].second;
- p_script->doc_tutorials.append(td);
- }
-#endif
p_script->native = Ref<GDScriptNativeClass>();
p_script->base = Ref<GDScript>();
@@ -2269,19 +2397,28 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
member_functions.clear();
+ p_script->static_variables.clear();
+
if (p_script->implicit_initializer) {
memdelete(p_script->implicit_initializer);
}
if (p_script->implicit_ready) {
memdelete(p_script->implicit_ready);
}
+ if (p_script->static_initializer) {
+ memdelete(p_script->static_initializer);
+ }
+
p_script->member_functions.clear();
p_script->member_indices.clear();
p_script->member_info.clear();
+ p_script->static_variables_indices.clear();
+ p_script->static_variables.clear();
p_script->_signals.clear();
p_script->initializer = nullptr;
p_script->implicit_initializer = nullptr;
p_script->implicit_ready = nullptr;
+ p_script->static_initializer = nullptr;
p_script->clearing = false;
@@ -2389,13 +2526,15 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
} else {
prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
}
-#ifdef TOOLS_ENABLED
- p_script->doc_variables[name] = variable->doc_description;
-#endif
- p_script->member_info[name] = prop_info;
- p_script->member_indices[name] = minfo;
- p_script->members.insert(name);
+ if (variable->is_static) {
+ minfo.index = p_script->static_variables_indices.size();
+ p_script->static_variables_indices[name] = minfo;
+ } else {
+ p_script->member_info[name] = prop_info;
+ p_script->member_indices[name] = minfo;
+ p_script->members.insert(name);
+ }
#ifdef TOOLS_ENABLED
if (variable->initializer != nullptr && variable->initializer->is_constant) {
@@ -2404,7 +2543,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
} else {
p_script->member_default_values.erase(name);
}
- p_script->member_lines[name] = variable->start_line;
#endif
} break;
@@ -2413,12 +2551,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
StringName name = constant->identifier->name;
p_script->constants.insert(name, constant->initializer->reduced_value);
-#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = constant->start_line;
- if (!constant->doc_description.is_empty()) {
- p_script->doc_constants[name] = constant->doc_description;
- }
-#endif
} break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
@@ -2426,18 +2558,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
StringName name = enum_value.identifier->name;
p_script->constants.insert(name, enum_value.value);
-#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = enum_value.identifier->start_line;
- if (!p_script->doc_enums.has("@unnamed_enums")) {
- p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc();
- p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums";
- }
- DocData::ConstantDoc const_doc;
- const_doc.name = enum_value.identifier->name;
- const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int.
- const_doc.description = enum_value.doc_description;
- p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc);
-#endif
} break;
case GDScriptParser::ClassNode::Member::SIGNAL: {
@@ -2450,11 +2570,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
parameters_names.write[j] = signal->parameters[j]->identifier->name;
}
p_script->_signals[name] = parameters_names;
-#ifdef TOOLS_ENABLED
- if (!signal->doc_description.is_empty()) {
- p_script->doc_signals[name] = signal->doc_description;
- }
-#endif
} break;
case GDScriptParser::ClassNode::Member::ENUM: {
@@ -2462,19 +2577,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
StringName name = enum_n->identifier->name;
p_script->constants.insert(name, enum_n->dictionary);
-#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = enum_n->start_line;
- p_script->doc_enums[name] = DocData::EnumDoc();
- p_script->doc_enums[name].name = name;
- p_script->doc_enums[name].description = enum_n->doc_description;
- for (int j = 0; j < enum_n->values.size(); j++) {
- DocData::ConstantDoc const_doc;
- const_doc.name = enum_n->values[j].identifier->name;
- const_doc.value = Variant(enum_n->values[j].value).operator String();
- const_doc.description = enum_n->values[j].doc_description;
- p_script->doc_enums[name].values.push_back(const_doc);
- }
-#endif
} break;
case GDScriptParser::ClassNode::Member::GROUP: {
@@ -2500,6 +2602,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
}
+ p_script->static_variables.resize(p_script->static_variables_indices.size());
+
parsed_classes.insert(p_script);
parsing_classes.erase(p_script);
@@ -2522,9 +2626,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
}
-#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = inner_class->start_line;
-#endif
p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants
}
@@ -2579,6 +2680,15 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
}
}
+ if (p_class->has_static_data) {
+ Error err = OK;
+ GDScriptFunction *func = _make_static_initializer(err, p_script, p_class);
+ p_script->static_initializer = func;
+ if (err) {
+ return err;
+ }
+ }
+
#ifdef DEBUG_ENABLED
//validate instances if keeping state
@@ -2617,7 +2727,7 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
//well, tough luck, not gonna do anything here
}
}
-#endif
+#endif // TOOLS_ENABLED
} else {
GDScriptInstance *gi = static_cast<GDScriptInstance *>(si);
gi->reload_members();
@@ -2626,7 +2736,9 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
E = N;
}
}
-#endif
+#endif //DEBUG_ENABLED
+
+ has_static_data = p_class->has_static_data;
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
@@ -2640,6 +2752,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
if (err) {
return err;
}
+
+ has_static_data = has_static_data || inner_class->has_static_data;
}
p_script->_init_rpc_methods_properties();
@@ -2726,9 +2840,9 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
return err;
}
-#ifdef TOOLS_ENABLED
- p_script->_update_doc();
-#endif
+ if (has_static_data && !root->annotated_static_unload) {
+ GDScriptCache::add_static_script(p_script);
+ }
return GDScriptCache::finish_compiling(main_script->get_path());
}
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 5328c17c73..2d15d461fb 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -52,6 +52,7 @@ class GDScriptCompiler {
HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
HashMap<StringName, GDScriptCodeGenerator::Address> locals;
List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
+ bool is_static = false;
GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) {
uint32_t addr = generator->add_local(p_name, p_type);
@@ -130,6 +131,7 @@ class GDScriptCompiler {
void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
+ GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
@@ -138,6 +140,7 @@ class GDScriptCompiler {
StringName source;
String error;
GDScriptParser::ExpressionNode *awaited_node = nullptr;
+ bool has_static_data = false;
public:
static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 0acc03be3d..45ad8792d9 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -851,7 +851,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
}
text += ")";
- incr = 3 + captures_count;
+ incr = 4 + captures_count;
} break;
case OPCODE_CREATE_SELF_LAMBDA: {
int instr_var_args = _code_ptr[++ip];
@@ -871,7 +871,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
}
text += ")";
- incr = 3 + captures_count;
+ incr = 4 + captures_count;
} break;
case OPCODE_JUMP: {
text += "jump ";
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index be33c7c591..f1ac234d28 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2992,6 +2992,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<MethodInfo> virtual_methods;
ClassDB::get_virtual_methods(class_name, &virtual_methods);
+
+ {
+ // Not truly a virtual method, but can also be "overridden".
+ MethodInfo static_init("_static_init");
+ static_init.return_val.type = Variant::NIL;
+ static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL;
+ virtual_methods.push_back(static_init);
+ }
+
for (const MethodInfo &mi : virtual_methods) {
String method_hint = mi.name;
if (method_hint.contains(":")) {
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 1a5e9eef53..390e562e6f 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -409,7 +409,8 @@ public:
ADDR_TYPE_STACK = 0,
ADDR_TYPE_CONSTANT = 1,
ADDR_TYPE_MEMBER = 2,
- ADDR_TYPE_MAX = 3,
+ ADDR_TYPE_STATIC_VAR = 3,
+ ADDR_TYPE_MAX = 4,
};
enum FixedAddresses {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index bcc116cda2..d3529154cf 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -81,6 +81,8 @@ GDScriptParser::GDScriptParser() {
// TODO: Should this be static?
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+ register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
+
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations.
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
@@ -623,7 +625,7 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
return false;
}
-GDScriptParser::ClassNode *GDScriptParser::parse_class() {
+GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
ClassNode *n_class = alloc_node<ClassNode>();
ClassNode *previous_class = current_class;
@@ -724,7 +726,7 @@ void GDScriptParser::parse_extends() {
}
template <class T>
-void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
+void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
#ifdef TOOLS_ENABLED
@@ -749,7 +751,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
- T *member = (this->*p_parse_function)();
+ T *member = (this->*p_parse_function)(p_is_static);
if (member == nullptr) {
return;
}
@@ -761,7 +763,11 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#ifdef TOOLS_ENABLED
// Consume doc comments.
class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
- if (has_comment(doc_comment_line)) {
+
+ // Check whether current line has a doc comment
+ if (has_comment(previous.start_line, true)) {
+ member->doc_description = get_doc_comment(previous.start_line, true);
+ } else if (has_comment(doc_comment_line, true)) {
if constexpr (std::is_same_v<T, ClassNode>) {
get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true);
} else {
@@ -799,10 +805,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
void GDScriptParser::parse_class_body(bool p_is_multiline) {
bool class_end = false;
+ bool next_is_static = false;
while (!class_end && !is_at_end()) {
- switch (current.type) {
+ GDScriptTokenizer::Token token = current;
+ switch (token.type) {
case GDScriptTokenizer::Token::VAR:
- parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
+ parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
+ if (next_is_static) {
+ current_class->has_static_data = true;
+ }
break;
case GDScriptTokenizer::Token::CONST:
parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
@@ -810,9 +821,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::SIGNAL:
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
break;
- case GDScriptTokenizer::Token::STATIC:
case GDScriptTokenizer::Token::FUNC:
- parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
+ parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
break;
case GDScriptTokenizer::Token::CLASS:
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
@@ -820,6 +830,13 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::ENUM:
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
break;
+ case GDScriptTokenizer::Token::STATIC: {
+ advance();
+ next_is_static = true;
+ if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) {
+ push_error(R"(Expected "func" or "var" after "static".)");
+ }
+ } break;
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
@@ -866,6 +883,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
advance();
break;
}
+ if (token.type != GDScriptTokenizer::Token::STATIC) {
+ next_is_static = false;
+ }
if (panic_mode) {
synchronize();
}
@@ -875,11 +895,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
}
}
-GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
- return parse_variable(true);
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
+ return parse_variable(p_is_static, true);
}
-GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
VariableNode *variable = alloc_node<VariableNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
@@ -889,6 +909,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
variable->identifier = parse_identifier();
variable->export_info.name = variable->identifier->name;
+ variable->is_static = p_is_static;
if (match(GDScriptTokenizer::Token::COLON)) {
if (check(GDScriptTokenizer::Token::NEWLINE)) {
@@ -1032,6 +1053,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_setter";
function->identifier = identifier;
+ function->is_static = p_variable->is_static;
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
@@ -1083,6 +1105,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_getter";
function->identifier = identifier;
+ function->is_static = p_variable->is_static;
FunctionNode *previous_function = current_function;
current_function = function;
@@ -1107,7 +1130,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
}
}
-GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
ConstantNode *constant = alloc_node<ConstantNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
@@ -1174,7 +1197,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
return parameter;
}
-GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
SignalNode *signal = alloc_node<SignalNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
@@ -1219,7 +1242,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
return signal;
}
-GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
+GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
bool named = false;
@@ -1368,23 +1391,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
}
}
+ if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) {
+ if (!p_function->is_static) {
+ push_error(R"(Static constructor must be declared static.)");
+ }
+ if (p_function->parameters.size() != 0) {
+ push_error(R"(Static constructor cannot have parameters.)");
+ }
+ current_class->has_static_data = true;
+ }
+
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
}
-GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
FunctionNode *function = alloc_node<FunctionNode>();
- bool _static = false;
- if (previous.type == GDScriptTokenizer::Token::STATIC) {
- // TODO: Improve message if user uses "static" with "var" or "const"
- if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
- complete_extents(function);
- return nullptr;
- }
- _static = true;
- }
-
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
@@ -1396,7 +1419,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
current_function = function;
function->identifier = parse_identifier();
- function->is_static = _static;
+ function->is_static = p_is_static;
SuiteNode *body = alloc_node<SuiteNode>();
SuiteNode *previous_suite = current_suite;
@@ -1608,11 +1631,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::VAR:
advance();
- result = parse_variable();
+ result = parse_variable(false, false);
break;
case GDScriptTokenizer::Token::CONST:
advance();
- result = parse_constant();
+ result = parse_constant(false);
break;
case GDScriptTokenizer::Token::IF:
advance();
@@ -1642,7 +1665,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) {
- if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
@@ -3296,8 +3319,15 @@ static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins
}
}
-bool GDScriptParser::has_comment(int p_line) {
- return tokenizer.get_comments().has(p_line);
+bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
+ bool has_comment = tokenizer.get_comments().has(p_line);
+ // If there are no comments or if we don't care whether the comment
+ // is a docstring, we have our result.
+ if (!p_must_be_doc || !has_comment) {
+ return has_comment;
+ }
+
+ return tokenizer.get_comments()[p_line].comment.begins_with("##");
}
String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
@@ -4090,6 +4120,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
+bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) {
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
+ ClassNode *p_class = static_cast<ClassNode *>(p_target);
+ if (p_class->annotated_static_unload) {
+ push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
+ return false;
+ }
+ p_class->annotated_static_unload = true;
+ return true;
+}
+
GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
switch (type) {
case CONSTANT:
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 29841ab060..8f0265510f 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -709,6 +709,8 @@ public:
ClassNode *outer = nullptr;
bool extends_used = false;
bool onready_used = false;
+ bool has_static_data = false;
+ bool annotated_static_unload = false;
String extends_path;
Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
DataType base_type;
@@ -847,6 +849,7 @@ public:
LOCAL_BIND, // Pattern bind.
MEMBER_SIGNAL,
MEMBER_VARIABLE,
+ STATIC_VARIABLE,
MEMBER_CONSTANT,
INHERITED_VARIABLE,
};
@@ -1202,6 +1205,7 @@ public:
bool onready = false;
PropertyInfo export_info;
int assignments = 0;
+ bool is_static = false;
#ifdef TOOLS_ENABLED
String doc_description;
#endif // TOOLS_ENABLED
@@ -1405,16 +1409,16 @@ private:
// Main blocks.
void parse_program();
- ClassNode *parse_class();
+ ClassNode *parse_class(bool p_is_static);
void parse_class_name();
void parse_extends();
void parse_class_body(bool p_is_multiline);
template <class T>
- void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
- SignalNode *parse_signal();
- EnumNode *parse_enum();
+ void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
+ SignalNode *parse_signal(bool p_is_static);
+ EnumNode *parse_enum(bool p_is_static);
ParameterNode *parse_parameter();
- FunctionNode *parse_function();
+ FunctionNode *parse_function(bool p_is_static);
void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
// Annotations
@@ -1431,14 +1435,15 @@ private:
bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target);
bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target);
+ bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target);
// Statements.
Node *parse_statement();
- VariableNode *parse_variable();
- VariableNode *parse_variable(bool p_allow_property);
+ VariableNode *parse_variable(bool p_is_static);
+ VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
void parse_property_getter(VariableNode *p_variable);
void parse_property_setter(VariableNode *p_variable);
- ConstantNode *parse_constant();
+ ConstantNode *parse_constant(bool p_is_static);
AssertNode *parse_assert();
BreakNode *parse_break();
ContinueNode *parse_continue();
@@ -1480,7 +1485,7 @@ private:
#ifdef TOOLS_ENABLED
// Doc comments.
int class_doc_line = 0x7FFFFFFF;
- bool has_comment(int p_line);
+ bool has_comment(int p_line, bool p_must_be_doc = false);
String get_doc_comment(int p_line, bool p_single_line = false);
void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class);
#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 7098e4cd40..4e18045f3d 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -680,10 +680,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool awaited = false;
#endif
#ifdef DEBUG_ENABLED
- int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
+ int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
#endif
- Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
+ Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
#ifdef DEBUG_ENABLED
OPCODE_WHILE(ip < _code_size) {
@@ -1651,10 +1651,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool was_freed = false;
Object *obj = ret->get_validated_object_with_check(was_freed);
- if (was_freed) {
- err_text = "Got a freed object as a result of the call.";
- OPCODE_BREAK;
- }
if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
err_text = R"(Trying to call an async function without "await".)";
OPCODE_BREAK;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 0cb8e3a2af..c7f72f4eed 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -185,6 +185,9 @@ String GDScriptWarning::get_message() const {
case ONREADY_WITH_EXPORT: {
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
}
+ case REDUNDANT_STATIC_UNLOAD: {
+ return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -254,6 +257,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
"ONREADY_WITH_EXPORT",
+ "REDUNDANT_STATIC_UNLOAD",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index f0123c518c..7972e6d993 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -86,6 +86,7 @@ public:
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
+ REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
WARNING_MAX,
};
@@ -130,6 +131,7 @@ public:
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
+ WARN, // REDUNDANT_STATIC_UNLOAD
};
static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 57405aa1ce..b8448d16c2 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -350,13 +350,13 @@ void GDScriptTestRunner::handle_cmdline() {
for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
String &cmd = E->get();
if (cmd == "--gdscript-generate-tests") {
- if (E->next() == nullptr) {
- ERR_PRINT("Needed a path for the test files.");
- exit(-1);
+ String path;
+ if (E->next()) {
+ path = E->next()->get();
+ } else {
+ path = "modules/gdscript/tests/scripts";
}
- const String &path = E->next()->get();
-
GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr);
bool completed = runner.generate_outputs();
@@ -566,6 +566,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
}
+ // Setup output handlers.
+ ErrorHandlerData error_data(&result, this);
+
+ _print_handler.userdata = &result;
+ _error_handler.userdata = &error_data;
+ add_print_handler(&_print_handler);
+ add_error_handler(&_error_handler);
+
script->reload();
// Create object instance for test.
@@ -577,14 +585,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
obj->set_script(script);
GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
- // Setup output handlers.
- ErrorHandlerData error_data(&result, this);
-
- _print_handler.userdata = &result;
- _error_handler.userdata = &error_data;
- add_print_handler(&_print_handler);
- add_error_handler(&_error_handler);
-
// Call test function.
Callable::CallError call_err;
instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd
new file mode 100644
index 0000000000..3dac751ba9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd
@@ -0,0 +1,5 @@
+static func _static_init() -> int:
+ print("static init")
+
+func test():
+ print("done")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out
new file mode 100644
index 0000000000..42294b7afb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Static constructor cannot have an explicit return type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd
new file mode 100644
index 0000000000..1ae814ea34
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd
@@ -0,0 +1,9 @@
+@static_unload
+
+func non_static():
+ return "non static"
+
+static var static_var = non_static()
+
+func test():
+ print("does not run")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
new file mode 100644
index 0000000000..f1e9ec34f2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static()" for static variable initializer.
diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd
new file mode 100644
index 0000000000..cbfa1f314f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd
@@ -0,0 +1,5 @@
+func _static_init():
+ print("static init")
+
+func test():
+ print("done")
diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out
new file mode 100644
index 0000000000..b2b8711e96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Static constructor must be declared static.
diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd
new file mode 100644
index 0000000000..711243f822
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd
@@ -0,0 +1,6 @@
+static func _static_init():
+ print("static init")
+ return true
+
+func test():
+ print("done")
diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out
new file mode 100644
index 0000000000..a034850e86
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Constructor cannot return a value.
diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd
new file mode 100644
index 0000000000..2a9fe851ef
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd
@@ -0,0 +1,31 @@
+# https://github.com/godotengine/godot/issues/75832
+
+@warning_ignore("narrowing_conversion")
+func test():
+ var hf := 2.0
+ var sf = 2.0
+
+ var i := 2
+ i *= hf
+ i *= sf
+ i *= 2.0
+ print(i)
+ var v2 := Vector2i(1, 2)
+ v2 *= hf
+ v2 *= sf
+ v2 *= 2.0
+ print(v2)
+ var v3 := Vector3i(1, 2, 3)
+ v3 *= hf
+ v3 *= sf
+ v3 *= 2.0
+ print(v3)
+ var v4 := Vector4i(1, 2, 3, 4)
+ v4 *= hf
+ v4 *= sf
+ v4 *= 2.0
+ print(v4)
+
+ var arr := [1, 2, 3]
+ arr += [4, 5]
+ print(arr)
diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_operator.out b/modules/gdscript/tests/scripts/runtime/features/assign_operator.out
new file mode 100644
index 0000000000..bfcfc1dff5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/assign_operator.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+16
+(8, 16)
+(8, 16, 24)
+(8, 16, 24, 32)
+[1, 2, 3, 4, 5]
diff --git a/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd
new file mode 100644
index 0000000000..a2d09bf7d3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd
@@ -0,0 +1,15 @@
+# https://github.com/godotengine/godot/issues/68184
+
+var node: Node:
+ get:
+ return node
+ set(n):
+ node = n
+
+
+func test():
+ node = Node.new()
+ node.free()
+
+ if !is_instance_valid(node):
+ print("It is freed")
diff --git a/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out
new file mode 100644
index 0000000000..b380f593d9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+It is freed
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd
new file mode 100644
index 0000000000..e08f77df12
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd
@@ -0,0 +1,13 @@
+@static_unload
+
+static var foo = "bar"
+
+static func _static_init():
+ print("static init called")
+ prints("foo is", foo)
+
+func test():
+ var _lambda = func _static_init():
+ print("lambda does not conflict with static constructor")
+
+ print("done")
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_constructor.out b/modules/gdscript/tests/scripts/runtime/features/static_constructor.out
new file mode 100644
index 0000000000..7f72a0ac2c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_constructor.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+static init called
+foo is bar
+done
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
new file mode 100644
index 0000000000..e193312381
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
@@ -0,0 +1,56 @@
+@static_unload
+
+static var perm := 0
+
+static var prop := "Hello!":
+ get: return prop + " suffix"
+ set(value): prop = "prefix " + str(value)
+
+static func get_data():
+ return "data"
+
+static var data = get_data()
+
+class Inner:
+ static var prop := "inner"
+ static func _static_init() -> void:
+ prints("Inner._static_init", prop)
+
+ class InnerInner:
+ static var prop := "inner inner"
+ static func _static_init() -> void:
+ prints("InnerInner._static_init", prop)
+
+func test():
+ prints("data:", data)
+
+ prints("perm:", perm)
+ prints("prop:", prop)
+
+ perm = 1
+ prop = "World!"
+
+ prints("perm:", perm)
+ prints("prop:", prop)
+
+ print("other.perm:", StaticVariablesOther.perm)
+ print("other.prop:", StaticVariablesOther.prop)
+
+ StaticVariablesOther.perm = 2
+ StaticVariablesOther.prop = "foo"
+
+ print("other.perm:", StaticVariablesOther.perm)
+ print("other.prop:", StaticVariablesOther.prop)
+
+ @warning_ignore("unsafe_method_access")
+ var path = get_script().get_path().get_base_dir()
+ var other = load(path + "/static_variables_load.gd")
+
+ print("load.perm:", other.perm)
+ print("load.prop:", other.prop)
+
+ other.perm = 3
+ other.prop = "bar"
+
+ print("load.perm:", other.perm)
+ print("load.prop:", other.prop)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.out b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
new file mode 100644
index 0000000000..d2491aef5e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
@@ -0,0 +1,16 @@
+GDTEST_OK
+Inner._static_init inner
+InnerInner._static_init inner inner
+data: data
+perm: 0
+prop: prefix Hello! suffix
+perm: 1
+prop: prefix World! suffix
+other.perm:0
+other.prop:prefix Hello! suffix
+other.perm:2
+other.prop:prefix foo suffix
+load.perm:0
+load.prop:prefix Hello! suffix
+load.perm:3
+load.prop:prefix bar suffix
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd
new file mode 100644
index 0000000000..8913b02756
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd
@@ -0,0 +1,10 @@
+@static_unload
+
+static var perm := 0
+
+static var prop := "Hello!":
+ get: return prop + " suffix"
+ set(value): prop = "prefix " + str(value)
+
+func test():
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd
new file mode 100644
index 0000000000..7a3b0acca6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd
@@ -0,0 +1,11 @@
+@static_unload
+class_name StaticVariablesOther
+
+static var perm := 0
+
+static var prop := "Hello!":
+ get: return prop + " suffix"
+ set(value): prop = "prefix " + str(value)
+
+func test():
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 5f6ec5904c..554fe5a422 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_file_dialog.h"
+#include "scene/gui/popup_menu.h"
String SceneExporterGLTFPlugin::get_name() const {
return "ConvertGLTF2";
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index 8e25d0fb84..e413560c16 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -73,14 +73,14 @@
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_collision_mask_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
- Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [code]layer_number[/code] between 1 and 32.
+ Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_meshes" qualifiers="const">
@@ -113,7 +113,7 @@
<return type="Vector3i[]" />
<param index="0" name="item" type="int" />
<description>
- Returns an array of all cells with the given item index specified in [code]item[/code].
+ Returns an array of all cells with the given item index specified in [param item].
</description>
</method>
<method name="local_to_map" qualifiers="const">
@@ -161,7 +161,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_layer], given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_collision_mask_value">
@@ -169,7 +169,7 @@
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
- Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [code]layer_number[/code] between 1 and 32.
+ Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_navigation_map">
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index ad9ab66cc9..34b9974d10 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -42,6 +42,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
#include "main/main.h"
@@ -156,11 +157,11 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() {
}
void godot_icall_Internal_EditorRunPlay() {
- EditorNode::get_singleton()->run_play();
+ EditorRunBar::get_singleton()->play_main_scene();
}
void godot_icall_Internal_EditorRunStop() {
- EditorNode::get_singleton()->run_stop();
+ EditorRunBar::get_singleton()->stop_playing();
}
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
index 36f69f2155..e6564a8aac 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
@@ -41,7 +41,7 @@
<return type="Node" />
<param index="0" name="data" type="Variant" default="null" />
<description>
- Requests a custom spawn, with [code]data[/code] passed to [member spawn_function] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
+ Requests a custom spawn, with [param data] passed to [member spawn_function] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
[b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns.
</description>
</method>
diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
index a3fb0c786e..2c93539ae1 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
@@ -17,14 +17,14 @@
<param index="0" name="filter" type="Callable" />
<description>
Adds a peer visibility filter for this synchronizer.
- [code]filter[/code] should take a peer ID [int] and return a [bool].
+ [param filter] should take a peer ID [int] and return a [bool].
</description>
</method>
<method name="get_visibility_for" qualifiers="const">
<return type="bool" />
<param index="0" name="peer" type="int" />
<description>
- Queries the current visibility for peer [code]peer[/code].
+ Queries the current visibility for peer [param peer].
</description>
</method>
<method name="remove_visibility_filter">
@@ -39,14 +39,14 @@
<param index="0" name="peer" type="int" />
<param index="1" name="visible" type="bool" />
<description>
- Sets the visibility of [code]peer[/code] to [code]visible[/code]. If [code]peer[/code] is [code]0[/code], the value of [member public_visibility] will be updated instead.
+ Sets the visibility of [param peer] to [param visible]. If [param peer] is [code]0[/code], the value of [member public_visibility] will be updated instead.
</description>
</method>
<method name="update_visibility">
<return type="void" />
<param index="0" name="for_peer" type="int" default="0" />
<description>
- Updates the visibility of [code]peer[/code] according to visibility filters. If [code]peer[/code] is [code]0[/code] (the default), all peers' visibilties are updated.
+ Updates the visibility of [param for_peer] according to visibility filters. If [param for_peer] is [code]0[/code] (the default), all peers' visibilties are updated.
</description>
</method>
</methods>
@@ -77,7 +77,7 @@
<signal name="visibility_changed">
<param index="0" name="for_peer" type="int" />
<description>
- Emitted when visibility of [code]for_peer[/code] is updated. See [method update_visibility].
+ Emitted when visibility of [param for_peer] is updated. See [method update_visibility].
</description>
</signal>
</signals>
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index 1ab37bfd82..2445d60f48 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -55,7 +55,7 @@
<param index="2" name="mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
<param index="3" name="channel" type="int" default="0" />
<description>
- 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.
+ Sends the given raw [param bytes] to a specific peer identified by [param id] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers.
</description>
</method>
</methods>
@@ -100,7 +100,7 @@
<param index="0" name="id" type="int" />
<param index="1" name="packet" type="PackedByteArray" />
<description>
- Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] receives a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
+ Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] receives a [param packet] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
</description>
</signal>
</signals>
diff --git a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
index 56ae219bab..53ea1d19a1 100644
--- a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
+++ b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
@@ -13,7 +13,7 @@
<param index="0" name="path" type="NodePath" />
<param index="1" name="index" type="int" default="-1" />
<description>
- Adds the property identified by the given [code]path[/code] to the list of the properties being synchronized, optionally passing an [code]index[/code].
+ Adds the property identified by the given [param path] to the list of the properties being synchronized, optionally passing an [param index].
</description>
</method>
<method name="get_properties" qualifiers="const">
@@ -26,28 +26,28 @@
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
- Returns whether the given [code]path[/code] is configured for synchronization.
+ Returns whether the given [param path] is configured for synchronization.
</description>
</method>
<method name="property_get_index" qualifiers="const">
<return type="int" />
<param index="0" name="path" type="NodePath" />
<description>
- Finds the index of the given [code]path[/code].
+ Finds the index of the given [param path].
</description>
</method>
<method name="property_get_spawn">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
- Returns whether the property identified by the given [code]path[/code] is configured to be synchronized on spawn.
+ Returns whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
<method name="property_get_sync">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
- Returns whether the property identified by the given [code]path[/code] is configured to be synchronized on process.
+ Returns whether the property identified by the given [param path] is configured to be synchronized on process.
</description>
</method>
<method name="property_set_spawn">
@@ -55,7 +55,7 @@
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
- Sets whether the property identified by the given [code]path[/code] is configured to be synchronized on spawn.
+ Sets whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
<method name="property_set_sync">
@@ -63,14 +63,14 @@
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
- Sets whether the property identified by the given [code]path[/code] is configured to be synchronized on process.
+ Sets whether the property identified by the given [param path] is configured to be synchronized on process.
</description>
</method>
<method name="remove_property">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<description>
- Removes the property identified by the given [code]path[/code] from the configuration.
+ Removes the property identified by the given [param path] from the configuration.
</description>
</method>
</methods>
diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp
index 20ccfe3906..83d31036a4 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/openxr_opengl_extension.cpp
@@ -37,7 +37,7 @@
#include "servers/rendering/rendering_server_globals.h"
#include "servers/rendering_server.h"
-// OpenXR requires us to submit sRGB textures so that it recognises the content
+// OpenXR requires us to submit sRGB textures so that it recognizes the content
// as being in sRGB color space. We do fall back on "normal" textures but this
// will likely result in incorrect colors as OpenXR will double the sRGB conversion.
// All major XR runtimes support sRGB textures.
diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml
index 1159cbb43a..a099a193bb 100644
--- a/modules/upnp/doc_classes/UPNP.xml
+++ b/modules/upnp/doc_classes/UPNP.xml
@@ -74,11 +74,11 @@
<param index="3" name="proto" type="String" default="&quot;UDP&quot;" />
<param index="4" name="duration" type="int" default="0" />
<description>
- Adds a mapping to forward the external [code]port[/code] (between 1 and 65535, although recommended to use port 1024 or above) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]"TCP"[/code] or [code]"UDP"[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. Note that forwarding a well-known port (below 1024) with UPnP may fail depending on the device.
+ Adds a mapping to forward the external [param port] (between 1 and 65535, although recommended to use port 1024 or above) on the default gateway (see [method get_gateway]) to the [param port_internal] on the local machine for the given protocol [param proto] (either [code]"TCP"[/code] or [code]"UDP"[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. Note that forwarding a well-known port (below 1024) with UPnP may fail depending on the device.
Depending on the gateway device, if a mapping for that port already exists, it will either be updated or it will refuse this command due to that conflict, especially if the existing mapping for that port wasn't created via UPnP or points to a different network address (or device) than this one.
- If [code]internal_port[/code] is [code]0[/code] (the default), the same port number is used for both the external and the internal port (the [code]port[/code] value).
- The description ([code]desc[/code]) is shown in some routers management UIs and can be used to point out which application added the mapping.
- The mapping's lease [code]duration[/code] can be limited by specifying a duration in seconds. The default of [code]0[/code] means no duration, i.e. a permanent lease and notably some devices only support these permanent leases. Note that whether permanent or not, this is only a request and the gateway may still decide at any point to remove the mapping (which usually happens on a reboot of the gateway, when its external IP address changes, or on some models when it detects a port mapping has become inactive, i.e. had no traffic for multiple minutes). If not [code]0[/code] (permanent), the allowed range according to spec is between [code]120[/code] (2 minutes) and [code]86400[/code] seconds (24 hours).
+ If [param port_internal] is [code]0[/code] (the default), the same port number is used for both the external and the internal port (the [param port] value).
+ The description ([param desc]) is shown in some routers management UIs and can be used to point out which application added the mapping.
+ The mapping's lease [param duration] can be limited by specifying a duration in seconds. The default of [code]0[/code] means no duration, i.e. a permanent lease and notably some devices only support these permanent leases. Note that whether permanent or not, this is only a request and the gateway may still decide at any point to remove the mapping (which usually happens on a reboot of the gateway, when its external IP address changes, or on some models when it detects a port mapping has become inactive, i.e. had no traffic for multiple minutes). If not [code]0[/code] (permanent), the allowed range according to spec is between [code]120[/code] (2 minutes) and [code]86400[/code] seconds (24 hours).
See [enum UPNPResult] for possible return values.
</description>
</method>
@@ -93,7 +93,7 @@
<param index="0" name="port" type="int" />
<param index="1" name="proto" type="String" default="&quot;UDP&quot;" />
<description>
- Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]"TCP"[/code] or [code]"UDP"[/code]. May be refused for mappings pointing to addresses other than this one, for well-known ports (below 1024), or for mappings not added via UPnP. See [enum UPNPResult] for possible return values.
+ Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [param port] must be a valid port between 1 and 65535, [param proto] can be either [code]"TCP"[/code] or [code]"UDP"[/code]. May be refused for mappings pointing to addresses other than this one, for well-known ports (below 1024), or for mappings not added via UPnP. See [enum UPNPResult] for possible return values.
</description>
</method>
<method name="discover">
@@ -103,7 +103,7 @@
<param index="2" name="device_filter" type="String" default="&quot;InternetGatewayDevice&quot;" />
<description>
Discovers local [UPNPDevice]s. Clears the list of previously discovered devices.
- Filters for IGD (InternetGatewayDevice) type devices by default, as those manage port forwarding. [code]timeout[/code] is the time to wait for responses in milliseconds. [code]ttl[/code] is the time-to-live; only touch this if you know what you're doing.
+ Filters for IGD (InternetGatewayDevice) type devices by default, as those manage port forwarding. [param timeout] is the time to wait for responses in milliseconds. [param ttl] is the time-to-live; only touch this if you know what you're doing.
See [enum UPNPResult] for possible return values.
</description>
</method>
@@ -111,7 +111,7 @@
<return type="UPNPDevice" />
<param index="0" name="index" type="int" />
<description>
- Returns the [UPNPDevice] at the given [code]index[/code].
+ Returns the [UPNPDevice] at the given [param index].
</description>
</method>
<method name="get_device_count" qualifiers="const">
@@ -136,7 +136,7 @@
<return type="void" />
<param index="0" name="index" type="int" />
<description>
- Removes the device at [code]index[/code] from the list of discovered devices.
+ Removes the device at [param index] from the list of discovered devices.
</description>
</method>
<method name="set_device">
@@ -144,7 +144,7 @@
<param index="0" name="index" type="int" />
<param index="1" name="device" type="UPNPDevice" />
<description>
- Sets the device at [code]index[/code] from the list of discovered devices to [code]device[/code].
+ Sets the device at [param index] from the list of discovered devices to [param device].
</description>
</method>
</methods>
diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
index 607b8c05e2..fa58bf5d58 100644
--- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
+++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
@@ -18,8 +18,8 @@
<param index="1" name="peer_id" type="int" />
<param index="2" name="unreliable_lifetime" type="int" default="1" />
<description>
- Add a new peer to the mesh with the given [code]peer_id[/code]. The [WebRTCPeerConnection] must be in state [constant WebRTCPeerConnection.STATE_NEW].
- Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]).
+ Add a new peer to the mesh with the given [param peer_id]. The [WebRTCPeerConnection] must be in state [constant WebRTCPeerConnection.STATE_NEW].
+ Three channels will be created for reliable, unreliable, and ordered transport. The value of [param unreliable_lifetime] will be passed to the [code]"maxPacketLifetime"[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]).
</description>
</method>
<method name="create_client">
@@ -27,8 +27,8 @@
<param index="0" name="peer_id" type="int" />
<param index="1" name="channels_config" type="Array" default="[]" />
<description>
- Initialize the multiplayer peer as a client with the given [code]peer_id[/code] (must be between 2 and 2147483647). In this mode, you should only call [method add_peer] once and with [code]peer_id[/code] of [code]1[/code]. This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying.
- You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
+ Initialize the multiplayer peer as a client with the given [param peer_id] (must be between 2 and 2147483647). In this mode, you should only call [method add_peer] once and with [param peer_id] of [code]1[/code]. This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying.
+ You can optionally specify a [param channels_config] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
</description>
</method>
<method name="create_mesh">
@@ -36,7 +36,7 @@
<param index="0" name="peer_id" type="int" />
<param index="1" name="channels_config" type="Array" default="[]" />
<description>
- Initialize the multiplayer peer as a mesh (i.e. all peers connect to each other) with the given [code]peer_id[/code] (must be between 1 and 2147483647).
+ Initialize the multiplayer peer as a mesh (i.e. all peers connect to each other) with the given [param peer_id] (must be between 1 and 2147483647).
</description>
</method>
<method name="create_server">
@@ -44,14 +44,14 @@
<param index="0" name="channels_config" type="Array" default="[]" />
<description>
Initialize the multiplayer peer as a server (with unique ID of [code]1[/code]). This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying.
- You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
+ You can optionally specify a [param channels_config] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
</description>
</method>
<method name="get_peer">
<return type="Dictionary" />
<param index="0" name="peer_id" type="int" />
<description>
- Returns a dictionary representation of the peer with given [code]peer_id[/code] with three keys. [code]connection[/code] containing the [WebRTCPeerConnection] to this peer, [code]channels[/code] an array of three [WebRTCDataChannel], and [code]connected[/code] a boolean representing if the peer connection is currently connected (all three channels are open).
+ Returns a dictionary representation of the peer with given [param peer_id] with three keys. [code]"connection"[/code] containing the [WebRTCPeerConnection] to this peer, [code]"channels"[/code] an array of three [WebRTCDataChannel], and [code]"connected"[/code] a boolean representing if the peer connection is currently connected (all three channels are open).
</description>
</method>
<method name="get_peers">
@@ -64,14 +64,14 @@
<return type="bool" />
<param index="0" name="peer_id" type="int" />
<description>
- Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though).
+ Returns [code]true[/code] if the given [param peer_id] is in the peers map (it might not be connected though).
</description>
</method>
<method name="remove_peer">
<return type="void" />
<param index="0" name="peer_id" type="int" />
<description>
- Remove the peer with given [code]peer_id[/code] from the mesh. If the peer was connected, and [signal MultiplayerPeer.peer_connected] was emitted for it, then [signal MultiplayerPeer.peer_disconnected] will be emitted.
+ Remove the peer with given [param peer_id] from the mesh. If the peer was connected, and [signal MultiplayerPeer.peer_connected] was emitted for it, then [signal MultiplayerPeer.peer_disconnected] will be emitted.
</description>
</method>
</methods>
diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
index 1e1f54d8b2..99a5381f0c 100644
--- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
+++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
@@ -35,9 +35,9 @@
<param index="0" name="label" type="String" />
<param index="1" name="options" type="Dictionary" default="{}" />
<description>
- Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW].
- There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code].
- Valid [code]options[/code] are:
+ Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [param label] and optionally configured via the [param options] dictionary. This method can only be called when the connection is in state [constant STATE_NEW].
+ There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]"negotiated"[/code] option set to [code]true[/code].
+ Valid [param options] are:
[codeblock]
{
"negotiated": true, # When set to true (default off), means the channel is negotiated out of band. "id" must be set too. "data_channel_received" will not be called.
@@ -83,8 +83,8 @@
<return type="int" enum="Error" />
<param index="0" name="configuration" type="Dictionary" default="{}" />
<description>
- Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection.
- Valid [code]options[/code] are:
+ Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [param configuration] options can be passed to configure the peer connection.
+ Valid [param configuration] options are:
[codeblock]
{
"iceServers": [
@@ -111,7 +111,7 @@
<return type="void" />
<param index="0" name="extension_class" type="StringName" />
<description>
- Sets the [code]extension_class[/code] as the default [WebRTCPeerConnectionExtension] returned when creating a new [WebRTCPeerConnection].
+ Sets the [param extension_class] as the default [WebRTCPeerConnectionExtension] returned when creating a new [WebRTCPeerConnection].
</description>
</method>
<method name="set_local_description">
@@ -129,8 +129,8 @@
<param index="1" name="sdp" type="String" />
<description>
Sets the SDP description of the remote peer. This should be called with the values generated by a remote peer and received over the signaling server.
- If [code]type[/code] is [code]offer[/code] the peer will emit [signal session_description_created] with the appropriate answer.
- If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created].
+ If [param type] is [code]"offer"[/code] the peer will emit [signal session_description_created] with the appropriate answer.
+ If [param type] is [code]"answer"[/code] the peer will start emitting [signal ice_candidate_created].
</description>
</method>
</methods>
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index 57feadcc99..ff94d4452a 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -32,7 +32,7 @@
<return type="WebSocketPeer" />
<param index="0" name="peer_id" type="int" />
<description>
- Returns the [WebSocketPeer] associated to the given [code]peer_id[/code].
+ Returns the [WebSocketPeer] associated to the given [param peer_id].
</description>
</method>
<method name="get_peer_address" qualifiers="const">
diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml
index 580adfd2d2..61f0be22f6 100644
--- a/modules/websocket/doc_classes/WebSocketPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketPeer.xml
@@ -61,7 +61,7 @@
<param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
Connects to the given URL. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
- [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
+ [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [param url] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
</description>
</method>
<method name="get_close_code" qualifiers="const">
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index ed162b1da2..9591afa536 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -109,7 +109,7 @@
<return type="int" enum="WebXRInterface.TargetRayMode" />
<param index="0" name="input_source_id" type="int" />
<description>
- Returns the target ray mode for the given [code]input_source_id[/code].
+ Returns the target ray mode for the given [param input_source_id].
This can help interpret the input coming from that input source. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource/targetRayMode]XRInputSource.targetRayMode[/url] for more information.
</description>
</method>
@@ -117,7 +117,7 @@
<return type="XRPositionalTracker" />
<param index="0" name="input_source_id" type="int" />
<description>
- Gets an [XRPositionalTracker] for the given [code]input_source_id[/code].
+ Gets an [XRPositionalTracker] for the given [param input_source_id].
In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with.
Use this method to get information about the input source that triggered one of these signals:
- [signal selectstart]
@@ -132,14 +132,14 @@
<return type="bool" />
<param index="0" name="input_source_id" type="int" />
<description>
- Returns [code]true[/code] if there is an active input source with the given [code]input_source_id[/code].
+ Returns [code]true[/code] if there is an active input source with the given [param input_source_id].
</description>
</method>
<method name="is_session_supported">
<return type="void" />
<param index="0" name="session_mode" type="String" />
<description>
- Checks if the given [code]session_mode[/code] is supported by the user's browser.
+ Checks if the given [param session_mode] is supported by the user's browser.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRSessionMode]WebXR's XRSessionMode[/url], including: [code]"immersive-vr"[/code], [code]"immersive-ar"[/code], and [code]"inline"[/code].
This method returns nothing, instead it emits the [signal session_supported] signal with the result.
</description>
@@ -229,7 +229,7 @@
<param index="0" name="message" type="String" />
<description>
Emitted by [method XRInterface.initialize] if the session fails to start.
- [code]message[/code] may optionally contain an error message from WebXR, or an empty string if no message is available.
+ [param message] may optionally contain an error message from WebXR, or an empty string if no message is available.
</description>
</signal>
<signal name="session_started">
@@ -242,7 +242,7 @@
<param index="0" name="session_mode" type="String" />
<param index="1" name="supported" type="bool" />
<description>
- Emitted by [method is_session_supported] to indicate if the given [code]session_mode[/code] is supported or not.
+ Emitted by [method is_session_supported] to indicate if the given [param session_mode] is supported or not.
</description>
</signal>
<signal name="squeeze">
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 0f0132a5d1..f52edf2b61 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -3306,6 +3306,8 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
#ifndef ANDROID_ENABLED
quit_request.set();
- check_for_changes_thread.wait_to_finish();
+ if (check_for_changes_thread.is_started()) {
+ check_for_changes_thread.wait_to_finish();
+ }
#endif
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 1a0087e18d..71339c9443 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -199,10 +199,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
-
- DisplayServerAndroid::get_singleton()->reset_window();
- DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
}
+ DisplayServerAndroid::get_singleton()->reset_window();
+ DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
}
}
}
@@ -244,7 +243,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
if (step.get() == 0) {
// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
- Main::setup2(Thread::get_caller_id());
+ Main::setup2();
input_handler = new AndroidInputHandler();
step.increment();
return true;
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 249ee4323c..ef2b0a256d 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -10,7 +10,7 @@
</tutorials>
<members>
<member name="application/app_store_team_id" type="String" setter="" getter="">
- Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organisational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
+ Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
</member>
<member name="application/bundle_identifier" type="String" setter="" getter="">
Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]).
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index b6d70048e3..1b925ff3e3 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/string/translation.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
+#include "editor/export/editor_export.h"
#include "platform/ios/logo_svg.gen.h"
#include "modules/modules_enabled.gen.h" // For svg.
@@ -111,13 +112,13 @@ static const LoadingScreenInfo loading_screen_infos[] = {
{ PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false },
{ PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false },
- { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, true },
- { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, true },
- { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, true },
- { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, true },
- { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, true },
- { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, true },
- { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, true }
+ { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, false },
+ { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, false },
+ { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, false },
+ { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, false },
+ { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, false },
+ { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, false },
+ { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, false }
};
String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 317c6575b3..9544cc761d 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
+#include "editor/export/editor_export.h"
#include "platform/linuxbsd/logo_svg.gen.h"
#include "platform/linuxbsd/run_icon_svg.gen.h"
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 1a9fd431c7..f57af3bbd7 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -46,7 +46,7 @@
Official export templates include [code]universal[/code] binaries only.
</member>
<member name="codesign/apple_team_id" type="String" setter="" getter="">
- Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organisational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
+ Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
</member>
<member name="codesign/certificate_file" type="String" setter="" getter="">
PKCS #12 certificate file used to sign [code].app[/code] bundle.
diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h
index c539f7f529..eb7a30203a 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -98,6 +98,7 @@ public:
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
virtual Error shell_open(String p_uri) override;
+ virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder) override;
virtual String get_locale() const override;
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 71a250153b..74cdef6f25 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -287,6 +287,27 @@ String OS_MacOS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return ret;
}
+Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+ bool open_folder = false;
+ if (DirAccess::dir_exists_absolute(p_path) && p_open_folder) {
+ open_folder = true;
+ }
+
+ if (!p_path.begins_with("file://")) {
+ p_path = String("file://") + p_path;
+ }
+
+ NSString *string = [NSString stringWithUTF8String:p_path.utf8().get_data()];
+ NSURL *uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
+
+ if (open_folder) {
+ [[NSWorkspace sharedWorkspace] openURL:uri];
+ } else {
+ [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[ uri ]];
+ }
+ return OK;
+}
+
Error OS_MacOS::shell_open(String p_uri) {
NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
NSURL *uri = [[NSURL alloc] initWithString:string];
diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index 85964c53e6..c20e3316a5 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -31,6 +31,7 @@
#include "export.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "export_plugin.h"
void register_uwp_exporter_types() {
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index 1d7b96d707..c6c67db3de 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -103,7 +103,7 @@ void AudioDriverWeb::_audio_driver_capture(int p_from, int p_samples) {
Error AudioDriverWeb::init() {
int latency = GLOBAL_GET("audio/driver/output_latency");
if (!audio_context.inited) {
- audio_context.mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ audio_context.mix_rate = _get_configured_mix_rate();
audio_context.channel_count = godot_audio_init(&audio_context.mix_rate, latency, &_state_change_callback, &_latency_update_callback);
audio_context.inited = true;
}
diff --git a/platform/web/export/export.cpp b/platform/web/export/export.cpp
index 54d9774da5..80c29024a8 100644
--- a/platform/web/export/export.cpp
+++ b/platform/web/export/export.cpp
@@ -31,6 +31,7 @@
#include "export.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "export_plugin.h"
void register_web_exporter_types() {
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index 7a62cd2a4a..876efdf864 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "platform/web/logo_svg.gen.h"
#include "platform/web/run_icon_svg.gen.h"
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 05b25eae03..1863a3083b 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
+#include "editor/export/editor_export.h"
#include "platform/windows/logo_svg.gen.h"
#include "platform/windows/run_icon_svg.gen.h"
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index dc8953b32c..35f57607ec 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -1637,6 +1637,7 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r
// Create the body.
RID body = ps->body_create();
bodies_coords[body] = E_cell;
+ bodies_layers[body] = q.layer;
ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC);
ps->body_set_space(body, space);
@@ -1692,6 +1693,7 @@ void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
for (RID body : p_quadrant->bodies) {
bodies_coords.erase(body);
+ bodies_layers.erase(body);
PhysicsServer2D::get_singleton()->free(body);
}
p_quadrant->bodies.clear();
@@ -2895,6 +2897,11 @@ Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) {
return bodies_coords[p_physics_body];
}
+int TileMap::get_layer_for_body_rid(RID p_physics_body) {
+ ERR_FAIL_COND_V_MSG(!bodies_layers.has(p_physics_body), int(), vformat("No tiles for the given body RID %d.", p_physics_body));
+ return bodies_layers[p_physics_body];
+}
+
void TileMap::fix_invalid_tiles() {
ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open.");
@@ -4154,6 +4161,7 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cell_tile_data", "layer", "coords", "use_proxies"), &TileMap::get_cell_tile_data, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid);
+ ClassDB::bind_method(D_METHOD("get_layer_for_body_rid", "body"), &TileMap::get_layer_for_body_rid);
ClassDB::bind_method(D_METHOD("get_pattern", "layer", "coords_array"), &TileMap::get_pattern);
ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 1f3c672f17..3c135d1317 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -219,6 +219,8 @@ private:
// Mapping for RID to coords.
HashMap<RID, Vector2i> bodies_coords;
+ // Mapping for RID to tile layer.
+ HashMap<RID, int> bodies_layers;
// Quadrants and internals management.
Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const;
@@ -396,6 +398,8 @@ public:
// For finding tiles from collision.
Vector2i get_coords_for_body_rid(RID p_physics_body);
+ // For getting their layers as well.
+ int get_layer_for_body_rid(RID p_physics_body);
// Fixing and clearing methods.
void fix_invalid_tiles();
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index 7f98bc1c98..5ba861dc06 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -860,7 +860,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
// Find next and see when to transition.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
- // Predict reamin time.
+ // Predict remaining time.
double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY.
if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) {
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index cad09a1d9f..b7dc1c4fbe 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -67,9 +67,11 @@ void ColorPicker::_notification(int p_what) {
for (int i = 0; i < SLIDER_COUNT; i++) {
labels[i]->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
+ sliders[i]->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
set_offset((Side)i, get_offset((Side)i) + theme_cache.content_margin);
}
alpha_label->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
+ alpha_label->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
set_offset((Side)0, get_offset((Side)0) + theme_cache.content_margin);
for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
@@ -122,6 +124,8 @@ void ColorPicker::_update_theme_item_cache() {
theme_cache.sv_height = get_theme_constant(SNAME("sv_height"));
theme_cache.h_width = get_theme_constant(SNAME("h_width"));
+ theme_cache.center_slider_grabbers = get_theme_constant(SNAME("center_slider_grabbers"));
+
theme_cache.screen_picker = get_theme_icon(SNAME("screen_picker"));
theme_cache.expanded_arrow = get_theme_icon(SNAME("expanded_arrow"));
theme_cache.folded_arrow = get_theme_icon(SNAME("folded_arrow"));
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 6e894efb00..018ae10955 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -213,6 +213,8 @@ private:
int sv_width = 0;
int h_width = 0;
+ bool center_slider_grabbers = true;
+
Ref<Texture2D> screen_picker;
Ref<Texture2D> expanded_arrow;
Ref<Texture2D> folded_arrow;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 03f5146110..6ef39c88d8 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1735,13 +1735,18 @@ real_t Control::get_stretch_ratio() const {
// Input events.
void Control::_call_gui_input(const Ref<InputEvent> &p_event) {
- emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it)
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); // Signal should be first, so it's possible to override an event (and then accept it).
+ }
if (!is_inside_tree() || get_viewport()->is_input_handled()) {
- return; //input was handled, abort
+ return; // Input was handled, abort.
+ }
+
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ GDVIRTUAL_CALL(_gui_input, p_event);
}
- GDVIRTUAL_CALL(_gui_input, p_event);
if (!is_inside_tree() || get_viewport()->is_input_handled()) {
- return; //input was handled, abort
+ return; // Input was handled, abort.
}
gui_input(p_event);
}
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index c23232c974..30f161d27a 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -897,7 +897,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
} else {
Ref<InputEventKey> k = p_event;
- if (k.is_valid() && k->get_unicode()) {
+ if (allow_search && k.is_valid() && k->get_unicode()) {
uint64_t now = OS::get_singleton()->get_ticks_msec();
uint64_t diff = now - search_time_msec;
uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec"));
@@ -1586,6 +1586,14 @@ bool ItemList::get_allow_reselect() const {
return allow_reselect;
}
+void ItemList::set_allow_search(bool p_allow) {
+ allow_search = p_allow;
+}
+
+bool ItemList::get_allow_search() const {
+ return allow_search;
+}
+
void ItemList::set_icon_scale(real_t p_scale) {
ERR_FAIL_COND(!Math::is_finite(p_scale));
icon_scale = p_scale;
@@ -1828,6 +1836,9 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_reselect", "allow"), &ItemList::set_allow_reselect);
ClassDB::bind_method(D_METHOD("get_allow_reselect"), &ItemList::get_allow_reselect);
+ ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &ItemList::set_allow_search);
+ ClassDB::bind_method(D_METHOD("get_allow_search"), &ItemList::get_allow_search);
+
ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height);
ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height);
@@ -1845,6 +1856,7 @@ void ItemList::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index d050f4a9d0..cd91f97410 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -90,6 +90,7 @@ private:
bool ensure_selected_visible = false;
bool same_column_width = false;
+ bool allow_search = true;
bool auto_height = false;
float auto_height_value = 0.0;
@@ -258,6 +259,9 @@ public:
void set_allow_reselect(bool p_allow);
bool get_allow_reselect() const;
+ void set_allow_search(bool p_allow);
+ bool get_allow_search() const;
+
void ensure_current_is_visible();
void sort_items_by_text();
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index 292a4cfea2..b9efa449cb 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -64,8 +64,8 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x;
- double grab_width = (double)grabber->get_size().width;
- double grab_height = (double)grabber->get_size().height;
+ double grab_width = (double)grabber->get_width();
+ double grab_height = (double)grabber->get_height();
double max = orientation == VERTICAL ? get_size().height - grab_height : get_size().width - grab_width;
if (orientation == VERTICAL) {
set_as_ratio(1 - (((double)grab.pos - (grab_height / 2.0)) / max));
@@ -103,7 +103,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
if (orientation == VERTICAL) {
motion = -motion;
}
- double areasize = orientation == VERTICAL ? size.height - grabber->get_size().height : size.width - grabber->get_size().width;
+ double areasize = orientation == VERTICAL ? size.height - grabber->get_height() : size.width - grabber->get_width();
if (areasize <= 0) {
return;
}
@@ -159,6 +159,9 @@ void Slider::_update_theme_item_cache() {
theme_cache.grabber_hl_icon = get_theme_icon(SNAME("grabber_highlight"));
theme_cache.grabber_disabled_icon = get_theme_icon(SNAME("grabber_disabled"));
theme_cache.tick_icon = get_theme_icon(SNAME("tick"));
+
+ theme_cache.center_grabber = get_theme_constant(SNAME("center_grabber"));
+ theme_cache.grabber_offset = get_theme_constant(SNAME("grabber_offset"));
}
void Slider::_notification(int p_what) {
@@ -213,39 +216,41 @@ void Slider::_notification(int p_what) {
if (orientation == VERTICAL) {
int widget_width = style->get_minimum_size().width;
- double areasize = size.height - grabber->get_size().height;
+ double areasize = size.height - (theme_cache.center_grabber ? 0 : grabber->get_height());
+ int grabber_shift = theme_cache.center_grabber ? grabber->get_height() / 2 : 0;
style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height)));
- grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_size().height / 2), Size2i(widget_width, areasize * ratio + grabber->get_size().height / 2)));
+ grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift), Size2i(widget_width, areasize * ratio + grabber->get_height() / 2 - grabber_shift)));
if (ticks > 1) {
- int grabber_offset = (grabber->get_size().height / 2 - tick->get_height() / 2);
+ int grabber_offset = (grabber->get_height() / 2 - tick->get_height() / 2);
for (int i = 0; i < ticks; i++) {
if (!ticks_on_borders && (i == 0 || i + 1 == ticks)) {
continue;
}
- int ofs = (i * areasize / (ticks - 1)) + grabber_offset;
+ int ofs = (i * areasize / (ticks - 1)) + grabber_offset - grabber_shift;
tick->draw(ci, Point2i((size.width - widget_width) / 2, ofs));
}
}
- grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2 + get_theme_constant(SNAME("grabber_offset")), size.height - ratio * areasize - grabber->get_size().height));
+ grabber->draw(ci, Point2i(size.width / 2 - grabber->get_width() / 2 + theme_cache.grabber_offset, size.height - ratio * areasize - grabber->get_height() + grabber_shift));
} else {
int widget_height = style->get_minimum_size().height;
- double areasize = size.width - grabber->get_size().width;
+ double areasize = size.width - (theme_cache.center_grabber ? 0 : grabber->get_size().width);
+ int grabber_shift = theme_cache.center_grabber ? -grabber->get_width() / 2 : 0;
style->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(size.width, widget_height)));
- grabber_area->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(areasize * ratio + grabber->get_size().width / 2, widget_height)));
+ grabber_area->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(areasize * ratio + grabber->get_width() / 2 + grabber_shift, widget_height)));
if (ticks > 1) {
- int grabber_offset = (grabber->get_size().width / 2 - tick->get_width() / 2);
+ int grabber_offset = (grabber->get_width() / 2 - tick->get_width() / 2);
for (int i = 0; i < ticks; i++) {
if ((!ticks_on_borders) && ((i == 0) || ((i + 1) == ticks))) {
continue;
}
- int ofs = (i * areasize / (ticks - 1)) + grabber_offset;
+ int ofs = (i * areasize / (ticks - 1)) + grabber_offset + grabber_shift;
tick->draw(ci, Point2i(ofs, (size.height - widget_height) / 2));
}
}
- grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2 + get_theme_constant(SNAME("grabber_offset"))));
+ grabber->draw(ci, Point2i(ratio * areasize + grabber_shift, size.height / 2 - grabber->get_height() / 2 + theme_cache.grabber_offset));
}
} break;
}
diff --git a/scene/gui/slider.h b/scene/gui/slider.h
index 42778684af..684445f2b3 100644
--- a/scene/gui/slider.h
+++ b/scene/gui/slider.h
@@ -58,6 +58,9 @@ class Slider : public Range {
Ref<Texture2D> grabber_hl_icon;
Ref<Texture2D> grabber_disabled_icon;
Ref<Texture2D> tick_icon;
+
+ bool center_grabber = false;
+ int grabber_offset = 0;
} theme_cache;
protected:
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e22890562f..18d00d519c 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -38,6 +38,7 @@
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "scene/gui/box_container.h"
+#include "scene/gui/text_edit.h"
#include "scene/main/window.h"
#include <limits.h>
@@ -166,6 +167,18 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const {
return cells[p_column].mode;
}
+/* multiline editable */
+void TreeItem::set_edit_multiline(int p_column, bool p_multiline) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ cells.write[p_column].edit_multiline = p_multiline;
+ _changed_notify(p_column);
+}
+
+bool TreeItem::is_edit_multiline(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), false);
+ return cells[p_column].edit_multiline;
+}
+
/* check mode */
void TreeItem::set_checked(int p_column, bool p_checked) {
ERR_FAIL_INDEX(p_column, cells.size());
@@ -1404,6 +1417,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode);
ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode);
+ ClassDB::bind_method(D_METHOD("set_edit_multiline", "column", "multiline"), &TreeItem::set_edit_multiline);
+ ClassDB::bind_method(D_METHOD("is_edit_multiline", "column"), &TreeItem::is_edit_multiline);
+
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);
@@ -1726,7 +1742,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
}
}
}
- int item_min_height = p_item->get_custom_minimum_height();
+ int item_min_height = MAX(theme_cache.font->get_height(theme_cache.font_size), p_item->get_custom_minimum_height());
if (height < item_min_height) {
height = item_min_height;
}
@@ -1757,7 +1773,7 @@ int Tree::get_item_height(TreeItem *p_item) const {
return height;
}
-void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) {
+void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) {
ERR_FAIL_COND(theme_cache.font.is_null());
Rect2i rect = p_rect;
@@ -1795,7 +1811,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
if (rtl && rect.size.width > 0) {
Point2 draw_pos = rect.position;
- draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+ draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
p_cell.text_buf->set_width(rect.size.width);
if (p_ol_size > 0 && p_ol_color.a > 0) {
p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color);
@@ -1815,7 +1831,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
if (!rtl && rect.size.width > 0) {
Point2 draw_pos = rect.position;
- draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+ draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
p_cell.text_buf->set_width(rect.size.width);
if (p_ol_size > 0 && p_ol_color.a > 0) {
p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color);
@@ -2108,12 +2124,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
Point2i text_pos = item_rect.position;
- text_pos.y += Math::floor((item_rect.size.y - p_item->cells[i].text_buf->get_size().y) / 2);
+ text_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
int text_width = p_item->cells[i].text_buf->get_size().x;
switch (p_item->cells[i].mode) {
case TreeItem::CELL_MODE_STRING: {
- draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
} break;
case TreeItem::CELL_MODE_CHECK: {
Ref<Texture2D> checked = theme_cache.checked;
@@ -2137,7 +2153,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
item_rect.size.x -= check_w;
item_rect.position.x += check_w;
- draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
} break;
case TreeItem::CELL_MODE_RANGE: {
@@ -2216,7 +2232,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (!p_item->cells[i].editable) {
- draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
break;
}
@@ -2244,7 +2260,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.position += theme_cache.custom_button->get_offset();
}
- draw_item_rect(p_item->cells.write[i], ir, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells.write[i], ir, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
downarrow->draw(ci, arrow_pos);
@@ -2975,7 +2991,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return item_h; // nothing found
}
-void Tree::_text_editor_modal_close() {
+void Tree::_text_editor_popup_modal_close() {
if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
Input::get_singleton()->is_key_pressed(Key::ENTER)) {
@@ -2986,10 +3002,51 @@ void Tree::_text_editor_modal_close() {
return;
}
- _text_editor_submit(text_editor->get_text());
+ if (!popup_edited_item) {
+ return;
+ }
+
+ if (popup_edited_item->is_edit_multiline(popup_edited_item_col) && popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_STRING) {
+ _apply_multiline_edit();
+ } else {
+ _line_editor_submit(line_editor->get_text());
+ }
+}
+
+void Tree::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
+ if (p_event->is_action_pressed("ui_text_newline_blank", true)) {
+ accept_event();
+ } else if (p_event->is_action_pressed("ui_text_newline")) {
+ popup_editor->hide();
+ _apply_multiline_edit();
+ accept_event();
+ }
+}
+
+void Tree::_apply_multiline_edit() {
+ if (!popup_edited_item) {
+ return;
+ }
+
+ if (popup_edited_item_col < 0 || popup_edited_item_col > columns.size()) {
+ return;
+ }
+
+ TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col];
+ switch (c.mode) {
+ case TreeItem::CELL_MODE_STRING: {
+ c.text = text_editor->get_text();
+ } break;
+ default: {
+ ERR_FAIL();
+ }
+ }
+
+ item_edited(popup_edited_item_col, popup_edited_item);
+ queue_redraw();
}
-void Tree::_text_editor_submit(String p_text) {
+void Tree::_line_editor_submit(String p_text) {
popup_editor->hide();
if (!popup_edited_item) {
@@ -3395,7 +3452,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
accept_event();
}
- if (k.is_valid()) { // Incremental search
+ if (allow_search && k.is_valid()) { // Incremental search
if (!k->is_pressed()) {
return;
@@ -3830,18 +3887,16 @@ bool Tree::edit_selected() {
popup_menu->popup();
popup_edited_item = s;
popup_edited_item_col = col;
- return true;
- } else if (c.mode == TreeItem::CELL_MODE_STRING || c.mode == TreeItem::CELL_MODE_RANGE) {
+ return true;
+ } else if ((c.mode == TreeItem::CELL_MODE_STRING && !c.edit_multiline) || c.mode == TreeItem::CELL_MODE_RANGE) {
Rect2 popup_rect;
int value_editor_height = c.mode == TreeItem::CELL_MODE_RANGE ? value_editor->get_minimum_size().height : 0;
// "floor()" centers vertically.
- Vector2 ofs(0, Math::floor((MAX(text_editor->get_minimum_size().height, rect.size.height - value_editor_height) - rect.size.height) / 2));
+ Vector2 ofs(0, Math::floor((MAX(line_editor->get_minimum_size().height, rect.size.height - value_editor_height) - rect.size.height) / 2));
- Point2i textedpos = get_screen_position() + rect.position - ofs;
- cache.text_editor_position = textedpos;
- popup_rect.position = textedpos;
+ popup_rect.position = get_screen_position() + rect.position - ofs;
popup_rect.size = rect.size;
// Account for icon.
@@ -3849,9 +3904,12 @@ bool Tree::edit_selected() {
popup_rect.position.x += icon_size.x;
popup_rect.size.x -= icon_size.x;
- text_editor->clear();
- text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));
- text_editor->select_all();
+ line_editor->clear();
+ line_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));
+ line_editor->select_all();
+ line_editor->show();
+
+ text_editor->hide();
if (c.mode == TreeItem::CELL_MODE_RANGE) {
popup_rect.size.y += value_editor_height;
@@ -3873,6 +3931,22 @@ bool Tree::edit_selected() {
popup_editor->popup();
popup_editor->child_controls_changed();
+ line_editor->grab_focus();
+
+ return true;
+ } else if (c.mode == TreeItem::CELL_MODE_STRING && c.edit_multiline) {
+ line_editor->hide();
+
+ text_editor->clear();
+ text_editor->set_text(c.text);
+ text_editor->select_all();
+ text_editor->show();
+
+ popup_editor->set_position(get_screen_position() + rect.position);
+ popup_editor->set_size(rect.size);
+ popup_editor->popup();
+ popup_editor->child_controls_changed();
+
text_editor->grab_focus();
return true;
@@ -4144,14 +4218,10 @@ void Tree::_notification(int p_what) {
case NOTIFICATION_TRANSFORM_CHANGED: {
if (popup_edited_item != nullptr) {
Rect2 rect = popup_edited_item->get_meta("__focus_rect");
- Vector2 ofs(0, (text_editor->get_size().height - rect.size.height) / 2);
- Point2i textedpos = get_global_position() + rect.position - ofs;
- if (cache.text_editor_position != textedpos) {
- cache.text_editor_position = textedpos;
- text_editor->set_position(textedpos);
- value_editor->set_position(textedpos + Point2i(0, text_editor->get_size().height));
- }
+ popup_editor->set_position(get_global_position() + rect.position);
+ popup_editor->set_size(rect.size);
+ popup_editor->child_controls_changed();
}
} break;
}
@@ -5246,6 +5316,14 @@ bool Tree::get_allow_reselect() const {
return allow_reselect;
}
+void Tree::set_allow_search(bool p_allow) {
+ allow_search = p_allow;
+}
+
+bool Tree::get_allow_search() const {
+ return allow_search;
+}
+
void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &Tree::clear);
ClassDB::bind_method(D_METHOD("create_item", "parent", "index"), &Tree::create_item, DEFVAL(Variant()), DEFVAL(-1));
@@ -5326,10 +5404,14 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_reselect", "allow"), &Tree::set_allow_reselect);
ClassDB::bind_method(D_METHOD("get_allow_reselect"), &Tree::get_allow_reselect);
+ ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &Tree::set_allow_search);
+ ClassDB::bind_method(D_METHOD("get_allow_search"), &Tree::get_allow_search);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "columns"), "set_columns", "get_columns");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "column_titles_visible"), "set_column_titles_visible", "are_column_titles_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_recursive_folding"), "set_enable_recursive_folding", "is_recursive_folding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");
@@ -5374,17 +5456,26 @@ Tree::Tree() {
popup_editor = memnew(Popup);
add_child(popup_editor, false, INTERNAL_MODE_FRONT);
+
popup_editor_vb = memnew(VBoxContainer);
- popup_editor->add_child(popup_editor_vb);
popup_editor_vb->add_theme_constant_override("separation", 0);
popup_editor_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
- text_editor = memnew(LineEdit);
- popup_editor_vb->add_child(text_editor);
+ popup_editor->add_child(popup_editor_vb);
+
+ line_editor = memnew(LineEdit);
+ line_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ line_editor->hide();
+ popup_editor_vb->add_child(line_editor);
+
+ text_editor = memnew(TextEdit);
text_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ text_editor->hide();
+ popup_editor_vb->add_child(text_editor);
+
value_editor = memnew(HSlider);
- popup_editor_vb->add_child(value_editor);
value_editor->set_v_size_flags(SIZE_EXPAND_FILL);
value_editor->hide();
+ popup_editor_vb->add_child(value_editor);
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
@@ -5398,8 +5489,9 @@ Tree::Tree() {
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
- text_editor->connect("text_submitted", callable_mp(this, &Tree::_text_editor_submit));
- popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_modal_close));
+ line_editor->connect("text_submitted", callable_mp(this, &Tree::_line_editor_submit));
+ text_editor->connect("gui_input", callable_mp(this, &Tree::_text_editor_gui_input));
+ popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_popup_modal_close));
popup_menu->connect("id_pressed", callable_mp(this, &Tree::popup_select));
value_editor->connect("value_changed", callable_mp(this, &Tree::value_editor_changed));
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 42fc719f46..75ce6b689d 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -36,8 +36,9 @@
#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/gui/slider.h"
-#include "scene/resources/text_line.h"
+#include "scene/resources/text_paragraph.h"
+class TextEdit;
class Tree;
class TreeItem : public Object {
@@ -61,8 +62,9 @@ private:
Ref<Texture2D> icon;
Rect2i icon_region;
String text;
+ bool edit_multiline = false;
String suffix;
- Ref<TextLine> text_buf;
+ Ref<TextParagraph> text_buf;
String language;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
@@ -198,6 +200,10 @@ public:
void set_cell_mode(int p_column, TreeCellMode p_mode);
TreeCellMode get_cell_mode(int p_column) const;
+ /* multiline editable */
+ void set_edit_multiline(int p_column, bool p_multiline);
+ bool is_edit_multiline(int p_column) const;
+
/* check mode */
void set_checked(int p_column, bool p_checked);
void set_indeterminate(int p_column, bool p_indeterminate);
@@ -436,7 +442,7 @@ private:
bool clip_content = false;
String title;
HorizontalAlignment title_alignment = HORIZONTAL_ALIGNMENT_CENTER;
- Ref<TextLine> text_buf;
+ Ref<TextParagraph> text_buf;
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
ColumnInfo() {
@@ -449,7 +455,8 @@ private:
VBoxContainer *popup_editor_vb = nullptr;
Popup *popup_editor = nullptr;
- LineEdit *text_editor = nullptr;
+ LineEdit *line_editor = nullptr;
+ TextEdit *text_editor = nullptr;
HSlider *value_editor = nullptr;
bool updating_value_editor = false;
uint64_t focus_in_id = 0;
@@ -469,12 +476,14 @@ private:
void update_item_cell(TreeItem *p_item, int p_col);
void update_item_cache(TreeItem *p_item);
//void draw_item_text(String p_text,const Ref<Texture2D>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color);
- void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
+ void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item);
void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod);
- void _text_editor_submit(String p_text);
- void _text_editor_modal_close();
+ void _line_editor_submit(String p_text);
+ void _apply_multiline_edit();
+ void _text_editor_popup_modal_close();
+ void _text_editor_gui_input(const Ref<InputEvent> &p_event);
void value_editor_changed(double p_value);
void popup_select(int p_option);
@@ -578,8 +587,6 @@ private:
TreeItem *hover_item = nullptr;
int hover_cell = -1;
- Point2i text_editor_position;
-
bool rtl = false;
} cache;
@@ -625,6 +632,7 @@ private:
bool scrolling = false;
bool allow_reselect = false;
+ bool allow_search = true;
bool force_edit_checkbox_only_on_checkbox = false;
@@ -753,6 +761,9 @@ public:
void set_allow_reselect(bool p_allow);
bool get_allow_reselect() const;
+ void set_allow_search(bool p_allow);
+ bool get_allow_search() const;
+
Size2 get_minimum_size() const override;
Tree();
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 2082736653..b947526e96 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -2791,7 +2791,9 @@ void Node::request_ready() {
}
void Node::_call_input(const Ref<InputEvent> &p_event) {
- GDVIRTUAL_CALL(_input, p_event);
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ GDVIRTUAL_CALL(_input, p_event);
+ }
if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) {
return;
}
@@ -2799,7 +2801,9 @@ void Node::_call_input(const Ref<InputEvent> &p_event) {
}
void Node::_call_shortcut_input(const Ref<InputEvent> &p_event) {
- GDVIRTUAL_CALL(_shortcut_input, p_event);
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ GDVIRTUAL_CALL(_shortcut_input, p_event);
+ }
if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) {
return;
}
@@ -2807,7 +2811,9 @@ void Node::_call_shortcut_input(const Ref<InputEvent> &p_event) {
}
void Node::_call_unhandled_input(const Ref<InputEvent> &p_event) {
- GDVIRTUAL_CALL(_unhandled_input, p_event);
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ GDVIRTUAL_CALL(_unhandled_input, p_event);
+ }
if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) {
return;
}
@@ -2815,7 +2821,9 @@ void Node::_call_unhandled_input(const Ref<InputEvent> &p_event) {
}
void Node::_call_unhandled_key_input(const Ref<InputEvent> &p_event) {
- GDVIRTUAL_CALL(_unhandled_key_input, p_event);
+ if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ GDVIRTUAL_CALL(_unhandled_key_input, p_event);
+ }
if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) {
return;
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index d16a12fcff..3d11e6647e 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1377,7 +1377,9 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) {
}
}
- emit_signal(SceneStringNames::get_singleton()->window_input, p_ev);
+ if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
+ emit_signal(SceneStringNames::get_singleton()->window_input, p_ev);
+ }
if (is_inside_tree()) {
push_input(p_ev);
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 06daa5a46f..1bfcf8d3ac 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -565,6 +565,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("grabber_disabled", "HSlider", icons["slider_grabber_disabled"]);
theme->set_icon("tick", "HSlider", icons["hslider_tick"]);
+ theme->set_constant("center_grabber", "HSlider", 0);
theme->set_constant("grabber_offset", "HSlider", 0);
// VSlider
@@ -578,6 +579,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("grabber_disabled", "VSlider", icons["slider_grabber_disabled"]);
theme->set_icon("tick", "VSlider", icons["vslider_tick"]);
+ theme->set_constant("center_grabber", "VSlider", 0);
theme->set_constant("grabber_offset", "VSlider", 0);
// SpinBox
@@ -909,6 +911,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("sv_height", "ColorPicker", 256 * scale);
theme->set_constant("h_width", "ColorPicker", 30 * scale);
theme->set_constant("label_width", "ColorPicker", 10 * scale);
+ theme->set_constant("center_slider_grabbers", "ColorPicker", 1);
theme->set_icon("folded_arrow", "ColorPicker", icons["arrow_right"]);
theme->set_icon("expanded_arrow", "ColorPicker", icons["arrow_down"]);
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 448e800900..c30e009356 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -946,14 +946,24 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
} else {
if (fw.is_null()) {
fw = FileAccess::open(p_path + ".depren", FileAccess::WRITE);
+
+ if (res_uid == ResourceUID::INVALID_ID) {
+ res_uid = ResourceSaver::get_resource_id_for_path(p_path);
+ }
+
+ String uid_text = "";
+ if (res_uid != ResourceUID::INVALID_ID) {
+ uid_text = " uid=\"" + ResourceUID::get_singleton()->id_to_text(res_uid) + "\"";
+ }
+
if (is_scene) {
- fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + "]\n");
+ fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
} else {
String script_res_text;
if (!script_class.is_empty()) {
script_res_text = "script_class=\"" + script_class + "\" ";
}
- fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + "]\n");
+ fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
}
}
diff --git a/servers/audio/audio_driver_dummy.cpp b/servers/audio/audio_driver_dummy.cpp
index e6257d9260..1d11c01b9a 100644
--- a/servers/audio/audio_driver_dummy.cpp
+++ b/servers/audio/audio_driver_dummy.cpp
@@ -41,7 +41,7 @@ Error AudioDriverDummy::init() {
samples_in = nullptr;
if (mix_rate == -1) {
- mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ mix_rate = _get_configured_mix_rate();
}
channels = get_channels();
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 0344bf322d..49991e41d3 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -115,6 +115,20 @@ void AudioDriver::input_buffer_write(int32_t sample) {
}
}
+int AudioDriver::_get_configured_mix_rate() {
+ StringName audio_driver_setting = "audio/driver/mix_rate";
+ int mix_rate = GLOBAL_GET(audio_driver_setting);
+
+ // In the case of invalid mix rate, let's default to a sensible value..
+ if (mix_rate <= 0) {
+ WARN_PRINT(vformat("Invalid mix rate of %d, consider reassigning setting \'%s\'. \nDefaulting mix rate to value %d.",
+ mix_rate, audio_driver_setting, AudioDriverManager::DEFAULT_MIX_RATE));
+ mix_rate = AudioDriverManager::DEFAULT_MIX_RATE;
+ }
+
+ return mix_rate;
+}
+
AudioDriver::SpeakerMode AudioDriver::get_speaker_mode_by_total_channels(int p_channels) const {
switch (p_channels) {
case 4:
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 155beb2000..6585043f63 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -66,6 +66,8 @@ protected:
void input_buffer_init(int driver_buffer_frames);
void input_buffer_write(int32_t sample);
+ int _get_configured_mix_rate();
+
#ifdef DEBUG_ENABLED
_FORCE_INLINE_ void start_counting_ticks() { prof_ticks = OS::get_singleton()->get_ticks_usec(); }
_FORCE_INLINE_ void stop_counting_ticks() { prof_time += OS::get_singleton()->get_ticks_usec() - prof_ticks; }
@@ -136,7 +138,6 @@ class AudioDriverManager {
MAX_DRIVERS = 10
};
- static const int DEFAULT_MIX_RATE = 44100;
static const int DEFAULT_OUTPUT_LATENCY = 15;
static AudioDriver *drivers[MAX_DRIVERS];
@@ -145,6 +146,8 @@ class AudioDriverManager {
static AudioDriverDummy dummy_driver;
public:
+ static const int DEFAULT_MIX_RATE = 44100;
+
static void add_driver(AudioDriver *p_driver);
static void initialize(int p_driver);
static int get_driver_count();
diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp
index 4253ea8610..57da55db4d 100644
--- a/servers/rendering/renderer_rd/environment/fog.cpp
+++ b/servers/rendering/renderer_rd/environment/fog.cpp
@@ -463,15 +463,19 @@ Fog::VolumetricFog::~VolumetricFog() {
if (fog_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(fog_uniform_set)) {
RD::get_singleton()->free(fog_uniform_set);
}
- if (process_uniform_set_density.is_valid() && RD::get_singleton()->uniform_set_is_valid(process_uniform_set_density)) {
- RD::get_singleton()->free(process_uniform_set_density);
- }
- if (process_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(process_uniform_set)) {
- RD::get_singleton()->free(process_uniform_set);
- }
- if (process_uniform_set2.is_valid() && RD::get_singleton()->uniform_set_is_valid(process_uniform_set2)) {
- RD::get_singleton()->free(process_uniform_set2);
+
+ // At this point, due to cascade deletions, the sets may no longer be valid, but still they must work as a group.
+ gi_dependent_sets.valid = RD::get_singleton()->uniform_set_is_valid(gi_dependent_sets.process_uniform_set_density);
+#ifdef DEV_ENABLED
+ gi_dependent_sets.assert_actual_validity();
+#endif
+ if (gi_dependent_sets.valid) {
+ RD::get_singleton()->free(gi_dependent_sets.copy_uniform_set);
+ RD::get_singleton()->free(gi_dependent_sets.process_uniform_set_density);
+ RD::get_singleton()->free(gi_dependent_sets.process_uniform_set);
+ RD::get_singleton()->free(gi_dependent_sets.process_uniform_set2);
}
+
if (sdfgi_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(sdfgi_uniform_set)) {
RD::get_singleton()->free(sdfgi_uniform_set);
}
@@ -713,7 +717,10 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RD::get_singleton()->compute_list_end();
}
- if (fog->process_uniform_set_density.is_null() || !RD::get_singleton()->uniform_set_is_valid(fog->process_uniform_set_density)) {
+#ifdef DEV_ENABLED
+ fog->gi_dependent_sets.assert_actual_validity();
+#endif
+ if (!fog->gi_dependent_sets.valid) {
//re create uniform set if needed
Vector<RD::Uniform> uniforms;
Vector<RD::Uniform> copy_uniforms;
@@ -910,9 +917,9 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
uniforms.push_back(u);
}
- fog->copy_uniform_set = RD::get_singleton()->uniform_set_create(copy_uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_COPY), 0);
+ fog->gi_dependent_sets.copy_uniform_set = RD::get_singleton()->uniform_set_create(copy_uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_COPY), 0);
- fog->process_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG), 0);
+ fog->gi_dependent_sets.process_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG), 0);
RID aux7 = uniforms.write[7].get_id(0);
RID aux8 = uniforms.write[8].get_id(0);
@@ -920,11 +927,13 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
uniforms.write[7].set_id(0, aux8);
uniforms.write[8].set_id(0, aux7);
- fog->process_uniform_set2 = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG), 0);
+ fog->gi_dependent_sets.process_uniform_set2 = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG), 0);
uniforms.remove_at(8);
uniforms.write[7].set_id(0, aux7);
- fog->process_uniform_set_density = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_DENSITY), 0);
+ fog->gi_dependent_sets.process_uniform_set_density = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_DENSITY), 0);
+
+ fog->gi_dependent_sets.valid = true;
}
bool using_sdfgi = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_gi_inject(p_settings.env) > 0.0001 && RendererSceneRenderRD::get_singleton()->environment_get_sdfgi_enabled(p_settings.env) && (p_settings.sdfgi.is_valid());
@@ -1067,7 +1076,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, volumetric_fog.process_pipelines[using_sdfgi ? VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_DENSITY_WITH_SDFGI : VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_DENSITY]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->process_uniform_set_density, 0);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->gi_dependent_sets.process_uniform_set_density, 0);
if (using_sdfgi) {
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->sdfgi_uniform_set, 1);
@@ -1078,7 +1087,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
// Copy fog to history buffer
if (RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_temporal_reprojection(p_settings.env)) {
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, volumetric_fog.process_pipelines[VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_COPY]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->copy_uniform_set, 0);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->gi_dependent_sets.copy_uniform_set, 0);
RD::get_singleton()->compute_list_dispatch_threads(compute_list, fog->width, fog->height, fog->depth);
RD::get_singleton()->compute_list_add_barrier(compute_list);
}
@@ -1090,7 +1099,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RENDER_TIMESTAMP("Filter Fog");
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, volumetric_fog.process_pipelines[VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FILTER]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->process_uniform_set, 0);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->gi_dependent_sets.process_uniform_set, 0);
RD::get_singleton()->compute_list_dispatch_threads(compute_list, fog->width, fog->height, fog->depth);
RD::get_singleton()->compute_list_end();
@@ -1101,7 +1110,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
compute_list = RD::get_singleton()->compute_list_begin();
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, volumetric_fog.process_pipelines[VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FILTER]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->process_uniform_set2, 0);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->gi_dependent_sets.process_uniform_set2, 0);
RD::get_singleton()->compute_list_dispatch_threads(compute_list, fog->width, fog->height, fog->depth);
RD::get_singleton()->compute_list_add_barrier(compute_list);
@@ -1112,7 +1121,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RD::get_singleton()->draw_command_begin_label("Integrate Fog");
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, volumetric_fog.process_pipelines[VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->process_uniform_set, 0);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, fog->gi_dependent_sets.process_uniform_set, 0);
RD::get_singleton()->compute_list_dispatch_threads(compute_list, fog->width, fog->height, 1);
RD::get_singleton()->compute_list_end(RD::BARRIER_MASK_RASTER);
diff --git a/servers/rendering/renderer_rd/environment/fog.h b/servers/rendering/renderer_rd/environment/fog.h
index 0b6bcc29fb..926da4026c 100644
--- a/servers/rendering/renderer_rd/environment/fog.h
+++ b/servers/rendering/renderer_rd/environment/fog.h
@@ -301,10 +301,25 @@ public:
RID emissive_map;
RID fog_uniform_set;
- RID copy_uniform_set;
- RID process_uniform_set_density;
- RID process_uniform_set;
- RID process_uniform_set2;
+
+ struct {
+ bool valid = false;
+ RID copy_uniform_set;
+ RID process_uniform_set_density;
+ RID process_uniform_set;
+ RID process_uniform_set2;
+
+#ifdef DEV_ENABLED
+ void assert_actual_validity() {
+ // It's all-or-nothing, or something else has changed that requires dev attention.
+ DEV_ASSERT(valid == RD::get_singleton()->uniform_set_is_valid(copy_uniform_set));
+ DEV_ASSERT(valid == RD::get_singleton()->uniform_set_is_valid(process_uniform_set_density));
+ DEV_ASSERT(valid == RD::get_singleton()->uniform_set_is_valid(process_uniform_set));
+ DEV_ASSERT(valid == RD::get_singleton()->uniform_set_is_valid(process_uniform_set2));
+ }
+#endif
+ } gi_dependent_sets;
+
RID sdfgi_uniform_set;
RID sky_uniform_set;
diff --git a/servers/rendering/renderer_rd/environment/gi.cpp b/servers/rendering/renderer_rd/environment/gi.cpp
index 08133bf8d6..52f09e1ccb 100644
--- a/servers/rendering/renderer_rd/environment/gi.cpp
+++ b/servers/rendering/renderer_rd/environment/gi.cpp
@@ -3493,6 +3493,9 @@ void GI::init(SkyRD *p_sky) {
if (RendererSceneRenderRD::get_singleton()->is_vrs_supported()) {
defines += "\n#define USE_VRS\n";
}
+ if (!RD::get_singleton()->sampler_is_format_supported_for_filter(RD::DATA_FORMAT_R8G8_UINT, RD::SAMPLER_FILTER_LINEAR)) {
+ defines += "\n#define SAMPLE_VOXEL_GI_NEAREST\n";
+ }
Vector<String> gi_modes;
@@ -3695,14 +3698,16 @@ void GI::setup_voxel_gi_instances(RenderDataRD *p_render_data, Ref<RenderSceneBu
if (p_render_buffers->has_custom_data(RB_SCOPE_FOG)) {
Ref<Fog::VolumetricFog> fog = p_render_buffers->get_custom_data(RB_SCOPE_FOG);
- if (RD::get_singleton()->uniform_set_is_valid(fog->fog_uniform_set)) {
- RD::get_singleton()->free(fog->fog_uniform_set);
- RD::get_singleton()->free(fog->process_uniform_set);
- RD::get_singleton()->free(fog->process_uniform_set2);
+#ifdef DEV_ENABLED
+ fog->gi_dependent_sets.assert_actual_validity();
+#endif
+ if (fog->gi_dependent_sets.valid) {
+ RD::get_singleton()->free(fog->gi_dependent_sets.copy_uniform_set);
+ RD::get_singleton()->free(fog->gi_dependent_sets.process_uniform_set_density);
+ RD::get_singleton()->free(fog->gi_dependent_sets.process_uniform_set);
+ RD::get_singleton()->free(fog->gi_dependent_sets.process_uniform_set2);
+ fog->gi_dependent_sets.valid = false;
}
- fog->fog_uniform_set = RID();
- fog->process_uniform_set = RID();
- fog->process_uniform_set2 = RID();
}
}
@@ -3929,7 +3934,6 @@ void GI::process_gi(Ref<RenderSceneBuffersRD> p_render_buffers, const RID *p_nor
u.append_id(material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED));
uniforms.push_back(u);
}
-
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
diff --git a/servers/rendering/renderer_rd/shaders/environment/gi.glsl b/servers/rendering/renderer_rd/shaders/environment/gi.glsl
index 459c4dcb1d..59af9501ba 100644
--- a/servers/rendering/renderer_rd/shaders/environment/gi.glsl
+++ b/servers/rendering/renderer_rd/shaders/environment/gi.glsl
@@ -4,6 +4,10 @@
#VERSION_DEFINES
+#ifdef SAMPLE_VOXEL_GI_NEAREST
+#extension GL_EXT_samplerless_texture_functions : enable
+#endif
+
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#define M_PI 3.141592
@@ -625,7 +629,11 @@ void process_gi(ivec2 pos, vec3 vertex, inout vec4 ambient_light, inout vec4 ref
#ifdef USE_VOXEL_GI_INSTANCES
{
+#ifdef SAMPLE_VOXEL_GI_NEAREST
+ uvec2 voxel_gi_tex = texelFetch(voxel_gi_buffer, pos, 0).rg;
+#else
uvec2 voxel_gi_tex = texelFetch(usampler2D(voxel_gi_buffer, linear_sampler), pos, 0).rg;
+#endif
roughness *= roughness;
//find arbitrary tangent and bitangent, then build a matrix
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 744fd051f5..4ff8bbfb85 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -723,6 +723,7 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("framebuffer_is_valid", "framebuffer"), &RenderingDevice::framebuffer_is_valid);
ClassDB::bind_method(D_METHOD("sampler_create", "state"), &RenderingDevice::_sampler_create);
+ ClassDB::bind_method(D_METHOD("sampler_is_format_supported_for_filter", "format", "sampler_filter"), &RenderingDevice::sampler_is_format_supported_for_filter);
ClassDB::bind_method(D_METHOD("vertex_buffer_create", "size_bytes", "data", "use_as_storage"), &RenderingDevice::vertex_buffer_create, DEFVAL(Vector<uint8_t>()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("vertex_format_create", "vertex_descriptions"), &RenderingDevice::_vertex_format_create);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 447627b08e..48246fa44a 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -653,6 +653,7 @@ public:
};
virtual RID sampler_create(const SamplerState &p_state) = 0;
+ virtual bool sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_sampler_filter) const = 0;
/**********************/
/**** VERTEX ARRAY ****/
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index ff1d55f905..0644f5918c 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -54,9 +54,9 @@ int ShaderPreprocessor::Tokenizer::get_index() const {
return index;
}
-void ShaderPreprocessor::Tokenizer::get_and_clear_generated(Vector<ShaderPreprocessor::Token> *r_out) {
- for (int i = 0; i < generated.size(); i++) {
- r_out->push_back(generated[i]);
+void ShaderPreprocessor::Tokenizer::get_and_clear_generated(LocalVector<char32_t> *r_out) {
+ for (uint32_t i = 0; i < generated.size(); i++) {
+ r_out->push_back(generated[i].text);
}
generated.clear();
}
@@ -1206,18 +1206,14 @@ Error ShaderPreprocessor::preprocess(State *p_state, const String &p_code, Strin
break;
}
+ // Add autogenerated tokens if there are any.
+ p_tokenizer.get_and_clear_generated(&output);
+
if (state->disabled) {
// Preprocessor was disabled.
// Read the rest of the file into the output.
output.push_back(t.text);
continue;
- } else {
- // Add autogenerated tokens.
- Vector<Token> generated;
- p_tokenizer.get_and_clear_generated(&generated);
- for (int i = 0; i < generated.size(); i++) {
- output.push_back(generated[i].text);
- }
}
if (t.text == '#') {
diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h
index f198af66f0..406b663228 100644
--- a/servers/rendering/shader_preprocessor.h
+++ b/servers/rendering/shader_preprocessor.h
@@ -83,7 +83,7 @@ private:
int line;
int index;
int size;
- Vector<Token> generated;
+ LocalVector<Token> generated;
private:
void add_generated(const Token &p_t);
@@ -95,7 +95,7 @@ private:
char32_t peek();
int consume_line_continuations(int p_offset);
- void get_and_clear_generated(Vector<Token> *r_out);
+ void get_and_clear_generated(LocalVector<char32_t> *r_out);
void backtrack(char32_t p_what);
LocalVector<Token> advance(char32_t p_what);
void skip_whitespace();
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 7e8f23a14f..98f9b3da65 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -339,6 +339,48 @@ TEST_CASE("[Object] Signals") {
CHECK_EQ(signals_after.size(), signals_after_empty_added.size());
}
+ SUBCASE("Deleting an object with connected signals should disconnect them") {
+ List<Object::Connection> signal_connections;
+
+ {
+ Object target;
+ target.add_user_signal(MethodInfo("my_custom_signal"));
+
+ ERR_PRINT_OFF;
+ target.connect("nonexistent_signal1", callable_mp(&object, &Object::notify_property_list_changed));
+ target.connect("my_custom_signal", callable_mp(&object, &Object::notify_property_list_changed));
+ target.connect("nonexistent_signal2", callable_mp(&object, &Object::notify_property_list_changed));
+ ERR_PRINT_ON;
+
+ signal_connections.clear();
+ object.get_all_signal_connections(&signal_connections);
+ CHECK(signal_connections.size() == 0);
+ signal_connections.clear();
+ object.get_signals_connected_to_this(&signal_connections);
+ CHECK(signal_connections.size() == 1);
+
+ ERR_PRINT_OFF;
+ object.connect("nonexistent_signal1", callable_mp(&target, &Object::notify_property_list_changed));
+ object.connect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed));
+ object.connect("nonexistent_signal2", callable_mp(&target, &Object::notify_property_list_changed));
+ ERR_PRINT_ON;
+
+ signal_connections.clear();
+ object.get_all_signal_connections(&signal_connections);
+ CHECK(signal_connections.size() == 1);
+ signal_connections.clear();
+ object.get_signals_connected_to_this(&signal_connections);
+ CHECK(signal_connections.size() == 1);
+ }
+
+ signal_connections.clear();
+ object.get_all_signal_connections(&signal_connections);
+ CHECK(signal_connections.size() == 0);
+ signal_connections.clear();
+ object.get_signals_connected_to_this(&signal_connections);
+ CHECK(signal_connections.size() == 0);
+ }
+
SUBCASE("Emitting a non existing signal will return an error") {
Error err = object.emit_signal("some_signal");
CHECK(err == ERR_UNAVAILABLE);