diff options
80 files changed, 914 insertions, 316 deletions
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index ed4ef01012..666b10d016 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -19,13 +19,6 @@ jobs: steps: - uses: actions/checkout@v3 - # Azure repositories are not reliable, we need to prevent azure giving us packages. - - name: Make apt sources.list use the default Ubuntu repositories - run: | - sudo rm -f /etc/apt/sources.list.d/* - sudo cp -f misc/ci/sources.list /etc/apt/sources.list - sudo apt-get update - - name: Set up Java 11 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index bfda7c72f8..46a9228616 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -74,16 +74,13 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Linux dependencies - shell: bash + # Need newer mesa for lavapipe to work properly. + - name: Linux dependencies for tests + if: ${{ matrix.proj-test }} run: | - # Azure repositories are not reliable, we need to prevent azure giving us packages. - sudo rm -f /etc/apt/sources.list.d/* - sudo cp -f misc/ci/sources.list /etc/apt/sources.list + sudo add-apt-repository ppa:kisak/kisak-mesa sudo apt-get update - # The actual dependencies - sudo apt-get install build-essential pkg-config libgl1-mesa-dev libglu-dev \ - xvfb wget unzip llvm + sudo apt-get install -qq mesa-vulkan-drivers - name: Setup Godot build cache uses: ./.github/actions/godot-cache @@ -138,16 +135,6 @@ jobs: ${{ matrix.bin }} --doctool --headless 2>&1 > /dev/null || true git diff --color --exit-code && ! git ls-files --others --exclude-standard | sed -e 's/^/New doc file missing in PR: /' | grep 'xml$' - # Download, unzip and setup SwiftShader library - # See https://github.com/godotengine/regression-test-project/releases/tag/_ci-deps for details - - name: Download SwiftShader - if: ${{ matrix.tests }} - run: | - wget https://github.com/godotengine/regression-test-project/releases/download/_ci-deps/swiftshader-ubuntu20.04-20211002.zip - unzip swiftshader-ubuntu20.04-20211002.zip - curr="$(pwd)/libvk_swiftshader.so" - sed -i "s|PATH_TO_CHANGE|$curr|" vk_swiftshader_icd.json - # Test 3.x -> 4.x project converter - name: Test project converter if: ${{ matrix.proj-conv }} @@ -155,7 +142,7 @@ jobs: mkdir converter_test cd converter_test touch project.godot - ../${{ matrix.bin }} --headless --audio-driver Dummy --validate-conversion-3to4 + ../${{ matrix.bin }} --headless --validate-conversion-3to4 cd .. rm converter_test -rf @@ -171,7 +158,7 @@ jobs: - name: Open and close editor (Vulkan) if: ${{ matrix.proj-test }} run: | - VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run ${{ matrix.bin }} --audio-driver Dummy --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true + xvfb-run ${{ matrix.bin }} --audio-driver Dummy --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true misc/scripts/check_ci_log.py sanitizers_log.txt - name: Open and close editor (GLES3) @@ -184,7 +171,7 @@ jobs: - name: Run project if: ${{ matrix.proj-test }} run: | - VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run ${{ matrix.bin }} 40 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true + xvfb-run ${{ matrix.bin }} 40 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true misc/scripts/check_ci_log.py sanitizers_log.txt # Checkout godot-cpp diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index eece270dd9..a3722e19ce 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -13,17 +13,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # Azure repositories are not reliable, we need to prevent Azure giving us packages. - - name: Make apt sources.list use the default Ubuntu repositories + - name: Install dependencies run: | - sudo rm -f /etc/apt/sources.list.d/* - sudo cp -f misc/ci/sources.list /etc/apt/sources.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main" sudo apt-get update - - - name: Install dependencies - run: | sudo apt-get install -qq dos2unix clang-format-15 libxml2-utils python3-pip moreutils sudo update-alternatives --remove-all clang-format || true sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-15 100 diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 39f8ad68e4..f0de22f2ef 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -40,6 +40,7 @@ #include "core/io/file_access_pack.h" #include "core/io/marshalls.h" #include "core/os/keyboard.h" +#include "core/variant/typed_array.h" #include "core/variant/variant_parser.h" #include "core/version.h" @@ -1136,20 +1137,27 @@ Variant ProjectSettings::get_setting(const String &p_setting, const Variant &p_d } } -Array ProjectSettings::get_global_class_list() { - Array script_classes; +TypedArray<Dictionary> ProjectSettings::get_global_class_list() { + if (is_global_class_list_loaded) { + return global_class_list; + } Ref<ConfigFile> cf; cf.instantiate(); if (cf->load(get_global_class_list_path()) == OK) { - script_classes = cf->get_value("", "list", Array()); + global_class_list = cf->get_value("", "list", Array()); } else { #ifndef TOOLS_ENABLED // Script classes can't be recreated in exported project, so print an error. ERR_PRINT("Could not load global script cache."); #endif } - return script_classes; + + // File read succeeded or failed. If it failed, assume everything is still okay. + // We will later receive updated class data in store_global_class_list(). + is_global_class_list_loaded = true; + + return global_class_list; } String ProjectSettings::get_global_class_list_path() const { @@ -1161,6 +1169,8 @@ void ProjectSettings::store_global_class_list(const Array &p_classes) { cf.instantiate(); cf->set_value("", "list", p_classes); cf->save(get_global_class_list_path()); + + global_class_list = p_classes; } bool ProjectSettings::has_custom_feature(const String &p_feature) const { @@ -1195,6 +1205,7 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("set_setting", "name", "value"), &ProjectSettings::set_setting); ClassDB::bind_method(D_METHOD("get_setting", "name", "default_value"), &ProjectSettings::get_setting, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("get_setting_with_override", "name"), &ProjectSettings::get_setting_with_override); + ClassDB::bind_method(D_METHOD("get_global_class_list"), &ProjectSettings::get_global_class_list); ClassDB::bind_method(D_METHOD("set_order", "name", "position"), &ProjectSettings::set_order); ClassDB::bind_method(D_METHOD("get_order", "name"), &ProjectSettings::get_order); ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value"), &ProjectSettings::set_initial_value); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index 70f697741f..50cb274831 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -37,6 +37,9 @@ #include "core/templates/local_vector.h" #include "core/templates/rb_set.h" +template <typename T> +class TypedArray; + class ProjectSettings : public Object { GDCLASS(ProjectSettings, Object); _THREAD_SAFE_CLASS_ @@ -99,6 +102,9 @@ protected: HashMap<StringName, AutoloadInfo> autoloads; + Array global_class_list; + bool is_global_class_list_loaded = false; + String project_data_dir_name; bool _set(const StringName &p_name, const Variant &p_value); @@ -141,7 +147,7 @@ public: void set_setting(const String &p_setting, const Variant &p_value); Variant get_setting(const String &p_setting, const Variant &p_default_value = Variant()) const; - Array get_global_class_list(); + TypedArray<Dictionary> get_global_class_list(); void store_global_class_list(const Array &p_classes); String get_global_class_list_path() const; diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 95812fc311..df9b6b3f1a 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -226,19 +226,16 @@ StringName::StringName(const char *p_name, bool p_static) { _data = _data->next; } - if (_data) { - if (_data->refcount.ref()) { - // exists - if (p_static) { - _data->static_count.increment(); - } + if (_data && _data->refcount.ref()) { + // exists + if (p_static) { + _data->static_count.increment(); + } #ifdef DEBUG_ENABLED - if (unlikely(debug_stringname)) { - _data->debug_references++; - } -#endif + if (unlikely(debug_stringname)) { + _data->debug_references++; } - +#endif return; } @@ -288,19 +285,17 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) { _data = _data->next; } - if (_data) { - if (_data->refcount.ref()) { - // exists - if (p_static) { - _data->static_count.increment(); - } + if (_data && _data->refcount.ref()) { + // exists + if (p_static) { + _data->static_count.increment(); + } #ifdef DEBUG_ENABLED - if (unlikely(debug_stringname)) { - _data->debug_references++; - } -#endif - return; + if (unlikely(debug_stringname)) { + _data->debug_references++; } +#endif + return; } _data = memnew(_Data); @@ -348,19 +343,17 @@ StringName::StringName(const String &p_name, bool p_static) { _data = _data->next; } - if (_data) { - if (_data->refcount.ref()) { - // exists - if (p_static) { - _data->static_count.increment(); - } + if (_data && _data->refcount.ref()) { + // exists + if (p_static) { + _data->static_count.increment(); + } #ifdef DEBUG_ENABLED - if (unlikely(debug_stringname)) { - _data->debug_references++; - } -#endif - return; + if (unlikely(debug_stringname)) { + _data->debug_references++; } +#endif + return; } _data = memnew(_Data); diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 8fc44d7536..50be9b86bf 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -173,14 +173,14 @@ <method name="rpc" qualifiers="vararg const"> <return type="void" /> <description> - Perform an RPC (Remote Procedure Call). This is used for multiplayer and is normally not available, unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. + Perform an RPC (Remote Procedure Call). This is used for multiplayer and is normally not available, unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. See [method Node.rpc]. </description> </method> <method name="rpc_id" qualifiers="vararg const"> <return type="void" /> <param index="0" name="peer_id" type="int" /> <description> - Perform an RPC (Remote Procedure Call) on a specific peer ID (see multiplayer documentation for reference). This is used for multiplayer and is normally not available unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. + Perform an RPC (Remote Procedure Call) on a specific peer ID (see multiplayer documentation for reference). This is used for multiplayer and is normally not available unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. See [method Node.rpc_id]. </description> </method> <method name="unbind" qualifiers="const"> diff --git a/doc/classes/CanvasGroup.xml b/doc/classes/CanvasGroup.xml index 6eeff8fef3..45f77ba484 100644 --- a/doc/classes/CanvasGroup.xml +++ b/doc/classes/CanvasGroup.xml @@ -8,6 +8,7 @@ [b]Note:[/b] The [CanvasGroup] uses a custom shader to read from the backbuffer to draw its children. Assigning a [Material] to the [CanvasGroup] overrides the builtin shader. To duplicate the behavior of the builtin shader in a custom [Shader] use the following: [codeblock] shader_type canvas_item; + render_mode unshaded; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; diff --git a/doc/classes/Decal.xml b/doc/classes/Decal.xml index fb8bc18c1a..b63f6e7252 100644 --- a/doc/classes/Decal.xml +++ b/doc/classes/Decal.xml @@ -8,6 +8,7 @@ They are made of an [AABB] and a group of [Texture2D]s specifying [Color], normal, ORM (ambient occlusion, roughness, metallic), and emission. Decals are projected within their [AABB] so altering the orientation of the Decal affects the direction in which they are projected. By default, Decals are projected down (i.e. from positive Y to negative Y). The [Texture2D]s associated with the Decal are automatically stored in a texture atlas which is used for drawing the decals so all decals can be drawn at once. Godot uses clustered decals, meaning they are stored in cluster data and drawn when the mesh is drawn, they are not drawn as a post-processing effect after. [b]Note:[/b] Decals cannot affect an underlying material's transparency, regardless of its transparency mode (alpha blend, alpha scissor, alpha hash, opaque pre-pass). This means translucent or transparent areas of a material will remain translucent or transparent even if an opaque decal is applied on them. + [b]Note:[/b] When using the Mobile rendering method, decals will only correctly affect meshes whose visibility AABB intersects with the decal's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the decal may not be visible on the mesh. </description> <tutorials> </tutorials> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 6f4a7fc13d..55ba1f4f0c 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1325,6 +1325,7 @@ <param index="1" name="window_id" type="int" default="0" /> <description> Sets the maximum size of the window specified by [param window_id] in pixels. Normally, the user will not be able to drag the window to make it smaller than the specified size. See also [method window_get_max_size]. + [b]Note:[/b] It's recommended to change this value using [member Window.max_size] instead. [b]Note:[/b] Using third-party tools, it is possible for users to disable window geometry restrictions and therefore bypass this limit. </description> </method> @@ -1334,6 +1335,7 @@ <param index="1" name="window_id" type="int" default="0" /> <description> Sets the minimum size for the given window to [param min_size] (in pixels). Normally, the user will not be able to drag the window to make it larger than the specified size. See also [method window_get_min_size]. + [b]Note:[/b] It's recommended to change this value using [member Window.min_size] instead. [b]Note:[/b] By default, the main window has a minimum size of [code]Vector2i(64, 64)[/code]. This prevents issues that can arise when the window is resized to a near-zero size. [b]Note:[/b] Using third-party tools, it is possible for users to disable window geometry restrictions and therefore bypass this limit. </description> @@ -1403,6 +1405,7 @@ +-------------+ +-------+ [/codeblock] See also [method window_get_position] and [method window_set_size]. + [b]Note:[/b] It's recommended to change this value using [member Window.position] instead. </description> </method> <method name="window_set_rect_changed_callback"> @@ -1419,6 +1422,7 @@ <param index="1" name="window_id" type="int" default="0" /> <description> Sets the size of the given window to [param size] (in pixels). See also [method window_get_size] and [method window_get_position]. + [b]Note:[/b] It's recommended to change this value using [member Window.size] instead. </description> </method> <method name="window_set_title"> @@ -1427,6 +1431,7 @@ <param index="1" name="window_id" type="int" default="0" /> <description> Sets the title of the given window to [param title]. + [b]Note:[/b] It's recommended to change this value using [member Window.title] instead. [b]Note:[/b] Avoid changing the window title every frame, as this can cause performance issues on certain window managers. Try to change the window title only a few times per second at most. </description> </method> @@ -1436,7 +1441,8 @@ <param index="1" name="parent_window_id" type="int" /> <description> Sets window transient parent. Transient window is will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode. - Note that behavior might be different depending on the platform. + [b]Note:[/b] It's recommended to change this value using [member Window.transient] instead. + [b]Note:[/b] The behavior might be different depending on the platform. </description> </method> <method name="window_set_vsync_mode"> diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml index 6a976d218f..66b61f187e 100644 --- a/doc/classes/EditorImportPlugin.xml +++ b/doc/classes/EditorImportPlugin.xml @@ -227,5 +227,15 @@ This method must be overridden to do the actual importing work. See this class' description for an example of overriding this method. </description> </method> + <method name="append_import_external_resource"> + <return type="int" enum="Error" /> + <param index="0" name="path" type="String" /> + <param index="1" name="custom_options" type="Dictionary" default="{}" /> + <param index="2" name="custom_importer" type="String" default="""" /> + <param index="3" name="generator_parameters" type="Variant" default="null" /> + <description> + This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data. + </description> + </method> </methods> </class> diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml index 69a7627774..a349c2b7b7 100644 --- a/doc/classes/FontFile.xml +++ b/doc/classes/FontFile.xml @@ -17,13 +17,13 @@ [codeblocks] [gdscript] var f = load("res://BarlowCondensed-Bold.ttf") - $"Label".set("custom_fonts/font", f) - $"Label".set("custom_fonts/font_size", 64) + $Label.add_theme_font_override("font", f) + $Label.add_theme_font_size_override("font_size", 64) [/gdscript] [csharp] var f = ResourceLoader.Load<FontFile>("res://BarlowCondensed-Bold.ttf"); - GetNode("Label").Set("custom_fonts/font", f); - GetNode("Label").Set("custom_font_sizes/font_size", 64); + GetNode("Label").AddThemeFontOverride("font", f); + GetNode("Label").AddThemeFontSizeOverride("font_size", 64); [/csharp] [/codeblocks] </description> @@ -88,6 +88,7 @@ <param index="0" name="cache_index" type="int" /> <param index="1" name="size" type="int" /> <description> + Returns the font descent (number of pixels below the baseline). </description> </method> <method name="get_cache_scale" qualifiers="const"> @@ -95,6 +96,7 @@ <param index="0" name="cache_index" type="int" /> <param index="1" name="size" type="int" /> <description> + Returns scaling factor of the color bitmap font. </description> </method> <method name="get_cache_underline_position" qualifiers="const"> @@ -102,6 +104,7 @@ <param index="0" name="cache_index" type="int" /> <param index="1" name="size" type="int" /> <description> + Returns pixel offset of the underline below the baseline. </description> </method> <method name="get_cache_underline_thickness" qualifiers="const"> @@ -109,6 +112,7 @@ <param index="0" name="cache_index" type="int" /> <param index="1" name="size" type="int" /> <description> + Returns thickness of the underline in pixels. </description> </method> <method name="get_embolden" qualifiers="const"> @@ -377,6 +381,7 @@ <param index="1" name="size" type="int" /> <param index="2" name="ascent" type="float" /> <description> + Sets the font ascent (number of pixels above the baseline). </description> </method> <method name="set_cache_descent"> @@ -385,6 +390,7 @@ <param index="1" name="size" type="int" /> <param index="2" name="descent" type="float" /> <description> + Sets the font descent (number of pixels below the baseline). </description> </method> <method name="set_cache_scale"> @@ -393,6 +399,7 @@ <param index="1" name="size" type="int" /> <param index="2" name="scale" type="float" /> <description> + Sets scaling factor of the color bitmap font. </description> </method> <method name="set_cache_underline_position"> @@ -401,6 +408,7 @@ <param index="1" name="size" type="int" /> <param index="2" name="underline_position" type="float" /> <description> + Sets pixel offset of the underline below the baseline. </description> </method> <method name="set_cache_underline_thickness"> @@ -409,6 +417,7 @@ <param index="1" name="size" type="int" /> <param index="2" name="underline_thickness" type="float" /> <description> + Sets thickness of the underline in pixels. </description> </method> <method name="set_embolden"> diff --git a/doc/classes/FontVariation.xml b/doc/classes/FontVariation.xml index e0fad126b9..5bc2606adb 100644 --- a/doc/classes/FontVariation.xml +++ b/doc/classes/FontVariation.xml @@ -10,16 +10,16 @@ [gdscript] var fv = FontVariation.new() fv.set_base_font(load("res://BarlowCondensed-Regular.ttf")) - fv.set_variation_embolden(1.2); - $"Label".set("custom_fonts/font", fv) - $"Label".set("custom_fonts/font_size", 64) + fv.set_variation_embolden(1.2) + $Label.add_theme_font_override("font", fv) + $Label.add_theme_font_size_override("font_size", 64) [/gdscript] [csharp] var fv = new FontVariation(); fv.SetBaseFont(ResourceLoader.Load<FontFile>("res://BarlowCondensed-Regular.ttf")); fv.SetVariationEmbolden(1.2); - GetNode("Label").Set("custom_fonts/font", fv); - GetNode("Label").Set("custom_font_sizes/font_size", 64); + GetNode("Label").AddThemeFontOverride("font", fv); + GetNode("Label").AddThemeFontSizeOverride("font_size", 64); [/csharp] [/codeblocks] </description> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7c40c189c0..bc43f228a7 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -648,7 +648,7 @@ <return type="int" enum="Error" /> <param index="0" name="method" type="StringName" /> <description> - Sends a remote procedure call request for the given [param method] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behavior depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns [code]null[/code]. + Sends a remote procedure call request for the given [param method] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behavior depends on the RPC configuration for the given method, see [method rpc_config] and [annotation @GDScript.@rpc]. Methods are not exposed to RPCs by default. Returns [code]null[/code]. [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().peer.get_connection_status() == CONNECTION_CONNECTED[/code]. </description> </method> @@ -666,7 +666,7 @@ channel = 0, } [/codeblock] - See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc("any")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs). + See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding [annotation @GDScript.@rpc] annotation ([code]@rpc("any_peer")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs). </description> </method> <method name="rpc_id" qualifiers="vararg"> diff --git a/doc/classes/OmniLight3D.xml b/doc/classes/OmniLight3D.xml index f71c81e713..c0e10574c8 100644 --- a/doc/classes/OmniLight3D.xml +++ b/doc/classes/OmniLight3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> An Omnidirectional light is a type of [Light3D] that emits light in all directions. The light is attenuated by distance and this attenuation can be configured by changing its energy, radius, and attenuation parameters. + [b]Note:[/b] When using the Mobile or Compatibility rendering methods, omni lights will only correctly affect meshes whose visibility AABB intersects with the light's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the light may not be visible on the mesh. </description> <tutorials> <link title="3D lights and shadows">$DOCS_URL/tutorials/3d/lights_and_shadows.html</link> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index e429759e93..c30747eac1 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -60,6 +60,18 @@ Clears the whole configuration (not recommended, may break things). </description> </method> + <method name="get_global_class_list"> + <return type="Dictionary[]" /> + <description> + Returns an [Array] of registered global classes. Each global class is represented as a [Dictionary] that contains the following entries: + - [code]base[/code] is a name of the base class; + - [code]class[/code] is a name of the registered global class; + - [code]icon[/code] is a path to a custom icon of the global class, if it has any; + - [code]language[/code] is a name of a programming language in which the global class is written; + - [code]path[/code] is a path to a file containing the global class. + [b]Note:[/b] Both the script and the icon paths are local to the project filesystem, i.e. they start with [code]res://[/code]. + </description> + </method> <method name="get_order" qualifiers="const"> <return type="int" /> <param index="0" name="name" type="String" /> @@ -405,9 +417,15 @@ <member name="debug/gdscript/warnings/function_used_as_property" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a function as if it is a property. </member> + <member name="debug/gdscript/warnings/get_node_default_without_onready" type="int" setter="" getter="" default="2"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when [method Node.get_node] (or the shorthand [code]$[/code]) is used as default value of a class variable without the [code]@onready[/code] annotation. + </member> <member name="debug/gdscript/warnings/incompatible_ternary" 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 ternary operator may emit values with incompatible types. </member> + <member name="debug/gdscript/warnings/inference_on_variant" type="int" setter="" getter="" default="2"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a static inferred type uses a [Variant] as initial value, which makes the static type to also be Variant. + </member> <member name="debug/gdscript/warnings/int_as_enum_without_cast" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum without an explicit cast. </member> @@ -420,6 +438,12 @@ <member name="debug/gdscript/warnings/narrowing_conversion" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when passing a floating-point value to a function that expects an integer (it will be converted and lose precision). </member> + <member name="debug/gdscript/warnings/native_method_override" type="int" setter="" getter="" default="2"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method in the script overrides a native method, because it may not behave as expected. + </member> + <member name="debug/gdscript/warnings/onready_with_export" type="int" setter="" getter="" default="2"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@onready[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected. + </member> <member name="debug/gdscript/warnings/property_used_as_function" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a property as if it is a function. </member> @@ -477,7 +501,7 @@ <member name="debug/gdscript/warnings/unsafe_property_access" type="int" setter="" getter="" default="0"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when accessing a property whose presence is not guaranteed at compile-time in the class. </member> - <member name="debug/gdscript/warnings/unsafe_void_return" type="int" setter="" getter="" default="0"> + <member name="debug/gdscript/warnings/unsafe_void_return" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when returning a call from a [code]void[/code] function when such call cannot be guaranteed to be also [code]void[/code]. </member> <member name="debug/gdscript/warnings/unused_local_constant" type="int" setter="" getter="" default="1"> diff --git a/doc/classes/ReflectionProbe.xml b/doc/classes/ReflectionProbe.xml index fa0b1ab00b..e912925cd2 100644 --- a/doc/classes/ReflectionProbe.xml +++ b/doc/classes/ReflectionProbe.xml @@ -7,6 +7,7 @@ Captures its surroundings as a cubemap, and stores versions of it with increasing levels of blur to simulate different material roughnesses. The [ReflectionProbe] is used to create high-quality reflections at a low performance cost (when [member update_mode] is [constant UPDATE_ONCE]). [ReflectionProbe]s can be blended together and with the rest of the scene smoothly. [ReflectionProbe]s can also be combined with [VoxelGI], SDFGI ([member Environment.sdfgi_enabled]) and screen-space reflections ([member Environment.ssr_enabled]) to get more accurate reflections in specific areas. [ReflectionProbe]s render all objects within their [member cull_mask], so updating them can be quite expensive. It is best to update them once with the important static objects and then leave them as-is. [b]Note:[/b] Unlike [VoxelGI] and SDFGI, [ReflectionProbe]s only source their environment from a [WorldEnvironment] node. If you specify an [Environment] resource within a [Camera3D] node, it will be ignored by the [ReflectionProbe]. This can lead to incorrect lighting within the [ReflectionProbe]. + [b]Note:[/b] When using the Mobile rendering method, reflection probes will only correctly affect meshes whose visibility AABB intersects with the reflection probe's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the reflection probe may not be visible on the mesh. </description> <tutorials> <link title="Reflection probes">$DOCS_URL/tutorials/3d/reflection_probes.html</link> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index 59d36aefab..9dff742748 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> A Spotlight is a type of [Light3D] node that emits lights in a specific direction, in the shape of a cone. The light is attenuated through the distance. This attenuation can be configured by changing the energy, radius and attenuation parameters of [Light3D]. + [b]Note:[/b] When using the Mobile or Compatibility rendering methods, spot lights will only correctly affect meshes whose visibility AABB intersects with the light's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the light may not be visible on the mesh. </description> <tutorials> <link title="3D lights and shadows">$DOCS_URL/tutorials/3d/lights_and_shadows.html</link> diff --git a/doc/classes/Variant.xml b/doc/classes/Variant.xml index 5416468ab6..390722b7e5 100644 --- a/doc/classes/Variant.xml +++ b/doc/classes/Variant.xml @@ -14,16 +14,21 @@ # bar = "Uh oh! I can't make static variables become a different type!" [/gdscript] [csharp] - // ... but C# is statically typed. Once a variable has a type it cannot be changed. However you can use the var keyword in methods to let the compiler decide the type automatically. - var foo = 2; // Foo is a 32-bit integer (int). Be cautious, integers in GDScript are 64-bit and the direct C# equivalent is "long". + // C# is statically typed. Once a variable has a type it cannot be changed. You can use the `var` keyword to let the compiler infer the type automatically. + var foo = 2; // Foo is a 32-bit integer (int). Be cautious, integers in GDScript are 64-bit and the direct C# equivalent is `long`. // foo = "foo was and will always be an integer. It cannot be turned into a string!"; var boo = "Boo is a string!"; - var ref = new Reference(); // var is especially useful when used together with a constructor. + var ref = new RefCounted(); // var is especially useful when used together with a constructor. + + // Godot also provides a Variant type that works like an union of all the Variant-compatible types. + Variant fooVar = 2; // fooVar is dynamically an integer (stored as a `long` in the Variant type). + fooVar = "Now fooVar is a string!"; + fooVar = new RefCounted(); // fooVar is a GodotObject. [/csharp] [/codeblocks] Godot tracks all scripting API variables within Variants. Without even realizing it, you use Variants all the time. When a particular language enforces its own rules for keeping data typed, then that language is applying its own custom logic over the base Variant scripting API. - GDScript automatically wrap values in them. It keeps all data in plain Variants by default and then optionally enforces custom static typing rules on variable types. - - C# is statically typed, but uses the Mono [code]object[/code] type in place of Godot's Variant class when it needs to represent a dynamic value. [code]object[/code] is the Mono runtime's equivalent of the same concept. + - C# is statically typed, but uses its own implementation of the [code]Variant[/code] type in place of Godot's Variant class when it needs to represent a dynamic value. A [code]Variant[/code] can be assigned any compatible type implicitly but converting requires an explicit cast. The global [method @GlobalScope.typeof] function returns the enumerated value of the Variant type stored in the current variable (see [enum Variant.Type]). [codeblocks] [gdscript] @@ -38,18 +43,24 @@ # To get the name of the underlying Object type, you need the `get_class()` method. print("foo is a(n) %s" % foo.get_class()) # inject the class name into a formatted string. # Note also that there is not yet any way to get a script's `class_name` string easily. - # To fetch that value, you can parse the [code]res://.godot/global_script_class_cache.cfg[/code] file with the [ConfigFile] API. + # To fetch that value, you can use [member ProjectSettings.get_global_class_list]. # Open your project.godot file to see it up close. [/gdscript] [csharp] - int foo = 2; - if (foo == null) + Variant foo = 2; + switch (foo.VariantType) { - GD.Print("foo is null"); - } - if (foo is int) - { - GD.Print("foo is an integer"); + case Variant.Type.Nil: + GD.Print("foo is null"); + break; + case Variant.Type.Int: + GD.Print("foo is an integer"); + break; + case Variant.Type.Object: + // Note that Objects are their own special category. + // You can convert a Variant to a GodotObject and use reflection to get its name. + GD.Print($"foo is a(n) {foo.AsGodotObject().GetType().Name}"); + break; } [/csharp] [/codeblocks] diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index ab2de14638..e76f805e3c 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -277,6 +277,10 @@ <member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false"> If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process. </member> + <member name="physics_object_picking_sort" type="bool" setter="set_physics_object_picking_sort" getter="get_physics_object_picking_sort" default="false"> + If [code]true[/code], objects receive mouse picking events sorted primarily by their [member CanvasItem.z_index] and secondarily by their position in the scene tree. If [code]false[/code], the order is undetermined. + [b]Note:[/b] This setting is disabled by default because of its potential expensive computational cost. + </member> <member name="positional_shadow_atlas_16_bits" type="bool" setter="set_positional_shadow_atlas_16_bits" getter="get_positional_shadow_atlas_16_bits" default="true"> </member> <member name="positional_shadow_atlas_quad_0" type="int" setter="set_positional_shadow_atlas_quadrant_subdiv" getter="get_positional_shadow_atlas_quadrant_subdiv" enum="Viewport.PositionalShadowAtlasQuadrantSubdiv" default="2"> diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index eb1b2f84b4..7f67651e62 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -2690,6 +2690,7 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() { // Default CanvasGroup shader. shader_type canvas_item; +render_mode unshaded; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; @@ -2717,6 +2718,7 @@ void fragment() { // Default clip children shader. shader_type canvas_item; +render_mode unshaded; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 8c7b52f379..ea0a0b660d 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -649,7 +649,7 @@ void main() { #ifdef MODE_LIGHT_ONLY color = vec4(0.0); -#else +#elif !defined(MODE_UNSHADED) color *= canvas_modulation; #endif diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index ee4163bc14..8426dfd1ac 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1108,7 +1108,7 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); } break; case Animation::TYPE_ROTATION_3D: { - p_list->push_back(PropertyInfo(Variant::QUATERNION, "scale")); + p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); } break; case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 644c32e8a4..ec1ef8a6bc 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1900,49 +1900,56 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector return err; } -void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> *p_custom_options, const String &p_custom_importer) { +Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) { EditorFileSystemDirectory *fs = nullptr; int cpos = -1; bool found = _find_file(p_file, &fs, cpos); - ERR_FAIL_COND_MSG(!found, "Can't find file '" + p_file + "'."); + ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'."); //try to obtain existing params - HashMap<StringName, Variant> params; + HashMap<StringName, Variant> params = p_custom_options; String importer_name; //empty by default though if (!p_custom_importer.is_empty()) { importer_name = p_custom_importer; } - if (p_custom_options != nullptr) { - params = *p_custom_options; - } ResourceUID::ID uid = ResourceUID::INVALID_ID; + Variant generator_parameters; + if (p_generator_parameters) { + generator_parameters = *p_generator_parameters; + } if (FileAccess::exists(p_file + ".import")) { //use existing - if (p_custom_options == nullptr) { - Ref<ConfigFile> cf; - cf.instantiate(); - Error err = cf->load(p_file + ".import"); - if (err == OK) { - if (cf->has_section("params")) { - List<String> sk; - cf->get_section_keys("params", &sk); - for (const String &E : sk) { + Ref<ConfigFile> cf; + cf.instantiate(); + Error err = cf->load(p_file + ".import"); + if (err == OK) { + if (cf->has_section("params")) { + List<String> sk; + cf->get_section_keys("params", &sk); + for (const String &E : sk) { + if (!params.has(E)) { params[E] = cf->get_value("params", E); } } + } + + if (cf->has_section("remap")) { + if (p_custom_importer.is_empty()) { + importer_name = cf->get_value("remap", "importer"); + } - if (cf->has_section("remap")) { - if (p_custom_importer.is_empty()) { - importer_name = cf->get_value("remap", "importer"); - } + if (cf->has_section_key("remap", "uid")) { + String uidt = cf->get_value("remap", "uid"); + uid = ResourceUID::get_singleton()->text_to_id(uidt); + } - if (cf->has_section_key("remap", "uid")) { - String uidt = cf->get_value("remap", "uid"); - uid = ResourceUID::get_singleton()->text_to_id(uidt); + if (!p_generator_parameters) { + if (cf->has_section_key("remap", "generator_parameters")) { + generator_parameters = cf->get_value("remap", "generator_parameters"); } } } @@ -1957,7 +1964,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String fs->files[cpos]->type = ""; fs->files[cpos]->import_valid = false; EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); - return; + return OK; } Ref<ResourceImporter> importer; bool load_default = false; @@ -1971,8 +1978,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String importer = ResourceFormatImporter::get_singleton()->get_importer_by_extension(p_file.get_extension()); load_default = true; if (importer.is_null()) { - ERR_PRINT("BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found."); - ERR_FAIL(); + ERR_FAIL_V_MSG(ERR_FILE_CANT_OPEN, "BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found."); } } @@ -2005,16 +2011,14 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String Variant meta; Error err = importer->import(p_file, base_path, params, &import_variants, &gen_files, &meta); - if (err != OK) { - ERR_PRINT("Error importing '" + p_file + "'."); - } + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + p_file + "'."); //as import is complete, save the .import file Vector<String> dest_paths; { Ref<FileAccess> f = FileAccess::open(p_file + ".import", FileAccess::WRITE); - ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file from path '" + p_file + ".import'."); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open file from path '" + p_file + ".import'."); //write manually, as order matters ([remap] has to go first for performance). f->store_line("[remap]"); @@ -2059,6 +2063,10 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String f->store_line("metadata=" + meta.get_construct_string()); } + if (generator_parameters != Variant()) { + f->store_line("generator_parameters=" + generator_parameters.get_construct_string()); + } + f->store_line(""); f->store_line("[deps]\n"); @@ -2102,7 +2110,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String // Store the md5's of the various files. These are stored separately so that the .import files can be version controlled. { Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::WRITE); - ERR_FAIL_COND_MSG(md5s.is_null(), "Cannot open MD5 file '" + base_path + ".md5'."); + ERR_FAIL_COND_V_MSG(md5s.is_null(), ERR_FILE_CANT_OPEN, "Cannot open MD5 file '" + base_path + ".md5'."); md5s->store_line("source_md5=\"" + FileAccess::get_md5(p_file) + "\""); if (dest_paths.size()) { @@ -2136,6 +2144,8 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String } EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); + + return OK; } void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap<String, Vector<String>> &group_files, HashSet<String> &groups_to_reimport) { @@ -2156,7 +2166,7 @@ void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap } void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params) { - _reimport_file(p_file, &p_custom_params, p_importer); + _reimport_file(p_file, p_custom_params, p_importer); } void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) { @@ -2166,10 +2176,11 @@ void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_im void EditorFileSystem::reimport_files(const Vector<String> &p_files) { importing = true; - EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size()); Vector<String> reloads; + EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size()); + Vector<ImportFile> reimport_files; HashSet<String> groups_to_reimport; @@ -2292,6 +2303,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { emit_signal(SNAME("resources_reimported"), reloads); } +Error EditorFileSystem::reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { + ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process."); + return _reimport_file(p_file, p_custom_options, p_custom_importer, &p_generator_parameters); +} + Error EditorFileSystem::_resource_import(const String &p_path) { Vector<String> files; files.push_back(p_path); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 2490bd31b3..0d558c84c5 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -242,7 +242,7 @@ class EditorFileSystem : public Node { void _update_extensions(); - void _reimport_file(const String &p_file, const HashMap<StringName, Variant> *p_custom_options = nullptr, const String &p_custom_importer = String()); + Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr); Error _reimport_group(const String &p_group_file, const Vector<String> &p_files); bool _test_for_reimport(const String &p_path, bool p_only_imported_files); @@ -315,6 +315,7 @@ public: EditorFileSystemDirectory *find_file(const String &p_file, int *r_index) const; void reimport_files(const Vector<String> &p_files); + Error reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters); void reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f317c23b83..f6fe6c9f76 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -852,6 +852,18 @@ void EditorNode::_remove_plugin_from_enabled(const String &p_name) { ps->set("editor_plugins/enabled", enabled_plugins); } +void EditorNode::_plugin_over_edit(EditorPlugin *p_plugin, Object *p_object) { + if (p_object) { + editor_plugins_over->add_plugin(p_plugin); + p_plugin->make_visible(true); + p_plugin->edit(p_object); + } else { + editor_plugins_over->remove_plugin(p_plugin); + p_plugin->make_visible(false); + p_plugin->edit(nullptr); + } +} + void EditorNode::_resources_changed(const Vector<String> &p_resources) { List<Ref<Resource>> changed; @@ -2102,8 +2114,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) { if (!item_plugins.has(plugin)) { // Remove plugins no longer used by this editing owner. to_remove.push_back(plugin); - plugin->make_visible(false); - plugin->edit(nullptr); + _plugin_over_edit(plugin, nullptr); } } @@ -2113,6 +2124,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) { for (EditorPlugin *plugin : item_plugins) { if (active_plugins[owner_id].has(plugin)) { + plugin->edit(p_object); continue; } @@ -2127,9 +2139,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) { } } active_plugins[owner_id].insert(plugin); - editor_plugins_over->add_plugin(plugin); - plugin->edit(p_object); - plugin->make_visible(true); + _plugin_over_edit(plugin, p_object); } } else { hide_unused_editors(p_editing_owner); @@ -2181,9 +2191,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) { if (p_editing_owner) { const ObjectID id = p_editing_owner->get_instance_id(); for (EditorPlugin *plugin : active_plugins[id]) { - plugin->make_visible(false); - plugin->edit(nullptr); - editor_plugins_over->remove_plugin(plugin); + _plugin_over_edit(plugin, nullptr); } active_plugins.erase(id); } else { @@ -2194,9 +2202,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) { if (!ObjectDB::get_instance(kv.key)) { to_remove.push_back(kv.key); for (EditorPlugin *plugin : kv.value) { - plugin->make_visible(false); - plugin->edit(nullptr); - editor_plugins_over->remove_plugin(plugin); + _plugin_over_edit(plugin, nullptr); } } } diff --git a/editor/editor_node.h b/editor/editor_node.h index 914dab0254..19a0e49a12 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -565,6 +565,7 @@ private: void _update_file_menu_closed(); void _remove_plugin_from_enabled(const String &p_name); + void _plugin_over_edit(EditorPlugin *p_plugin, Object *p_object); void _fs_changed(); void _resources_reimported(const Vector<String> &p_resources); diff --git a/editor/import/editor_import_plugin.cpp b/editor/import/editor_import_plugin.cpp index ef3d3d1276..7afce116b8 100644 --- a/editor/import/editor_import_plugin.cpp +++ b/editor/import/editor_import_plugin.cpp @@ -31,6 +31,7 @@ #include "editor_import_plugin.h" #include "core/object/script_language.h" +#include "editor/editor_file_system.h" EditorImportPlugin::EditorImportPlugin() { } @@ -185,6 +186,20 @@ Error EditorImportPlugin::import(const String &p_source_file, const String &p_sa ERR_FAIL_V_MSG(ERR_METHOD_NOT_FOUND, "Unimplemented _import in add-on."); } +Error EditorImportPlugin::_append_import_external_resource(const String &p_file, const Dictionary &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { + HashMap<StringName, Variant> options; + List<Variant> keys; + p_custom_options.get_key_list(&keys); + for (const Variant &K : keys) { + options.insert(K, p_custom_options[K]); + } + return append_import_external_resource(p_file, options, p_custom_importer, p_generator_parameters); +} + +Error EditorImportPlugin::append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { + return EditorFileSystem::get_singleton()->reimport_append(p_file, p_custom_options, p_custom_importer, p_generator_parameters); +} + void EditorImportPlugin::_bind_methods() { GDVIRTUAL_BIND(_get_importer_name) GDVIRTUAL_BIND(_get_visible_name) @@ -198,4 +213,5 @@ void EditorImportPlugin::_bind_methods() { GDVIRTUAL_BIND(_get_import_order) GDVIRTUAL_BIND(_get_option_visibility, "path", "option_name", "options") GDVIRTUAL_BIND(_import, "source_file", "save_path", "options", "platform_variants", "gen_files"); + ClassDB::bind_method(D_METHOD("append_import_external_resource", "path", "custom_options", "custom_importer", "generator_parameters"), &EditorImportPlugin::_append_import_external_resource, DEFVAL(Dictionary()), DEFVAL(String()), DEFVAL(Variant())); } diff --git a/editor/import/editor_import_plugin.h b/editor/import/editor_import_plugin.h index bf912058a2..fb164c7f15 100644 --- a/editor/import/editor_import_plugin.h +++ b/editor/import/editor_import_plugin.h @@ -53,6 +53,8 @@ protected: GDVIRTUAL3RC(bool, _get_option_visibility, String, StringName, Dictionary) GDVIRTUAL5RC(Error, _import, String, String, Dictionary, TypedArray<String>, TypedArray<String>) + Error _append_import_external_resource(const String &p_file, const Dictionary &p_custom_options = Dictionary(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant()); + public: EditorImportPlugin(); virtual String get_importer_name() const override; @@ -67,6 +69,7 @@ public: virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const override; virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override; virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata = nullptr) override; + Error append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant()); }; #endif // EDITOR_IMPORT_PLUGIN_H diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 14b5f7cefb..c41bf4b8cc 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1171,32 +1171,29 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { if (!p_frames.is_valid()) { frames.unref(); + hide(); return; } frames = p_frames; read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames); - if (p_frames.is_valid()) { - if (!p_frames->has_animation(edited_anim)) { - List<StringName> anim_names; - frames->get_animation_list(&anim_names); - anim_names.sort_custom<StringName::AlphCompare>(); - if (anim_names.size()) { - edited_anim = anim_names.front()->get(); - } else { - edited_anim = StringName(); - } + if (!p_frames->has_animation(edited_anim)) { + List<StringName> anim_names; + frames->get_animation_list(&anim_names); + anim_names.sort_custom<StringName::AlphCompare>(); + if (anim_names.size()) { + edited_anim = anim_names.front()->get(); + } else { + edited_anim = StringName(); } - - _update_library(); - // Clear zoom and split sheet texture - split_sheet_preview->set_texture(Ref<Texture2D>()); - _zoom_reset(); - } else { - hide(); } + _update_library(); + // Clear zoom and split sheet texture + split_sheet_preview->set_texture(Ref<Texture2D>()); + _zoom_reset(); + add_anim->set_disabled(read_only); delete_anim->set_disabled(read_only); anim_speed->set_editable(!read_only); diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 26f872421e..706466a974 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -30,13 +30,14 @@ #include "project_converter_3_to_4.h" -#include "modules/modules_enabled.gen.h" - #ifndef DISABLE_DEPRECATED -#ifdef MODULE_REGEX_ENABLED const int ERROR_CODE = 77; +#include "modules/modules_enabled.gen.h" // For regex. + +#ifdef MODULE_REGEX_ENABLED + #include "modules/regex/regex.h" #include "core/io/dir_access.h" diff --git a/editor/project_converter_3_to_4.h b/editor/project_converter_3_to_4.h index 6ec2dd188d..641bc467ac 100644 --- a/editor/project_converter_3_to_4.h +++ b/editor/project_converter_3_to_4.h @@ -29,9 +29,10 @@ /**************************************************************************/ #ifndef PROJECT_CONVERTER_3_TO_4_H -#ifndef DISABLE_DEPRECATED #define PROJECT_CONVERTER_3_TO_4_H +#ifndef DISABLE_DEPRECATED + #include "core/io/file_access.h" #include "core/object/ref_counted.h" #include "core/string/ustring.h" diff --git a/misc/ci/sources.list b/misc/ci/sources.list deleted file mode 100644 index 4d8f94f35c..0000000000 --- a/misc/ci/sources.list +++ /dev/null @@ -1,4 +0,0 @@ -deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse -deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse -deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse -deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 32acad76aa..e05b17168d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -565,14 +565,22 @@ </annotation> <annotation name="@rpc" qualifiers="vararg"> <return type="void" /> - <param index="0" name="mode" type="String" default="""" /> - <param index="1" name="sync" type="String" default="""" /> - <param index="2" name="transfer_mode" type="String" default="""" /> + <param index="0" name="mode" type="String" default=""authority"" /> + <param index="1" name="sync" type="String" default=""call_remote"" /> + <param index="2" name="transfer_mode" type="String" default=""unreliable"" /> <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]. [codeblock] - @rpc() + @rpc + func fn(): pass + + @rpc("any_peer", "unreliable_ordered") + func fn_update_pos(): pass + + @rpc("authority", "call_remote", "unreliable", 0) # Equivalent to @rpc + func fn_default(): pass [/codeblock] </description> </annotation> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index fd04d3c660..cafc7328e0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -138,13 +138,25 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co } static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { - GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta); + // Find out which base class declared the enum, so the name is always the same even when coming from other contexts. + StringName native_base = p_native_class; + while (true && native_base != StringName()) { + if (ClassDB::has_enum(native_base, p_enum_name, true)) { + break; + } + native_base = ClassDB::get_parent_class_nocheck(native_base); + } + + GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta); + if (p_meta) { + type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries + } List<StringName> enum_values; - ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); + ClassDB::get_enum_constants(native_base, p_enum_name, &enum_values, true); for (const StringName &E : enum_values) { - type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); + type.enum_values[E] = ClassDB::get_integer_constant(native_base, E); } return type; @@ -782,6 +794,22 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; { +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + GDScriptParser::Node *member_node = member.get_source_node(); + if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) { + // Apply @warning_ignore annotations before resolving member. + for (GDScriptParser::AnnotationNode *&E : member_node->annotations) { + if (E->name == SNAME("@warning_ignore")) { + resolve_annotation(E); + E->apply(parser, member.variable); + } + } + for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } + } +#endif switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); @@ -790,9 +818,48 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { - resolve_annotation(E); - E->apply(parser, member.variable); + if (E->name != SNAME("@warning_ignore")) { + resolve_annotation(E); + E->apply(parser, member.variable); + } + } +#ifdef DEBUG_ENABLED + if (member.variable->exported && member.variable->onready) { + parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT); } + 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)) { + GDScriptParser::Node *expr = member.variable->initializer; + if (expr->type == GDScriptParser::Node::CAST) { + expr = static_cast<GDScriptParser::CastNode *>(expr)->operand; + } + bool is_get_node = expr->type == GDScriptParser::Node::GET_NODE; + bool is_using_shorthand = is_get_node; + if (!is_get_node && expr->type == GDScriptParser::Node::CALL) { + is_using_shorthand = false; + GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(expr); + if (call->function_name == SNAME("get_node")) { + switch (call->get_callee_type()) { + case GDScriptParser::Node::IDENTIFIER: { + is_get_node = true; + } break; + case GDScriptParser::Node::SUBSCRIPT: { + GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(call->callee); + is_get_node = subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF; + } break; + default: + break; + } + } + } + if (is_get_node) { + parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, is_using_shorthand ? "$" : "get_node()"); + } + } + } +#endif } break; case GDScriptParser::ClassNode::Member::CONSTANT: { check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); @@ -878,6 +945,10 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, } } break; case GDScriptParser::ClassNode::Member::FUNCTION: + for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { + resolve_annotation(E); + E->apply(parser, member.function); + } resolve_function_signature(member.function, p_source); break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { @@ -931,6 +1002,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, ERR_PRINT("Trying to resolve undefined member."); break; } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif } parser->current_class = previous_class; @@ -1059,19 +1133,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_annotation(E); E->apply(parser, member.function); } - -#ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : member.function->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); - } -#endif // DEBUG_ENABLED - resolve_function_body(member.function); - -#ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; -#endif // DEBUG_ENABLED } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) { if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (member.variable->getter != nullptr) { @@ -1102,9 +1164,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { #ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : member.function->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : member.variable->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); } if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); @@ -1179,7 +1241,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } } #ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED } } @@ -1289,6 +1351,11 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); + if (p_annotation->is_resolved) { + return; + } + p_annotation->is_resolved = true; + const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info; const List<PropertyInfo>::Element *E = annotation_info.arguments.front(); @@ -1355,6 +1422,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } p_function->resolved_signature = true; +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } +#endif + GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; @@ -1421,7 +1495,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_par_count = 0; bool is_static = false; bool is_vararg = false; - if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) { + StringName native_base; + if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg, &native_base)) { bool valid = p_function->is_static == is_static; valid = valid && parent_return_type == p_function->get_datatype(); @@ -1447,8 +1522,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parameter = "Variant"; } parent_signature += parameter; - if (j == parameters_types.size() - default_par_count) { - parent_signature += " = default"; + if (j >= parameters_types.size() - default_par_count) { + parent_signature += " = <default>"; } j++; @@ -1464,6 +1539,11 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } +#ifdef DEBUG_ENABLED + if (native_base != StringName()) { + parser->push_warning(p_function, GDScriptWarning::NATIVE_METHOD_OVERRIDE, function_name, native_base); + } +#endif } #endif // TOOLS_ENABLED } @@ -1472,6 +1552,9 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->set_datatype(prev_datatype); } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif parser->current_function = previous_function; } @@ -1481,6 +1564,13 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } p_function->resolved_body = true; +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } +#endif + GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; @@ -1498,6 +1588,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif parser->current_function = previous_function; } @@ -1538,16 +1631,16 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { } #ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : stmt->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : stmt->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); } #endif // DEBUG_ENABLED resolve_node(stmt); #ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED decide_suite_type(p_suite, stmt); @@ -1599,6 +1692,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) { push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer); } +#ifdef DEBUG_ENABLED + if (initializer_type.is_hard_type() && initializer_type.is_variant()) { + parser->push_warning(p_assignable, GDScriptWarning::INFERENCE_ON_VARIANT, p_kind); + } +#endif } else { if (!initializer_type.is_set()) { push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer); @@ -2953,7 +3051,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a // Enums do not have functions other than the built-in dictionary ones. if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { - push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + if (base_type.builtin_type == Variant::DICTIONARY) { + push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + } else { + push_error(vformat(R"*(The native enum "%s" does not behave like Dictionary and does not have methods of its own.)*", base_type.enum_type), p_call->callee); + } } else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else. GDScriptParser::IdentifierNode *callee_id; if (callee_type == GDScriptParser::Node::IDENTIFIER) { @@ -4011,7 +4113,6 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar if (!is_type_compatible(true_type, false_type)) { result = false_type; if (!is_type_compatible(false_type, true_type)) { - result.type_source = GDScriptParser::DataType::UNDETECTED; result.kind = GDScriptParser::DataType::VARIANT; #ifdef DEBUG_ENABLED parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY); @@ -4019,6 +4120,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar } } } + result.type_source = true_type.is_hard_type() && false_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; p_ternary_op->set_datatype(result); } @@ -4334,15 +4436,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } elem_type.is_constant = false; result.set_container_element_type(elem_type); + } else if (p_property.type == Variant::INT) { + // Check if it's enum. + if (p_property.class_name != StringName()) { + Vector<String> names = String(p_property.class_name).split("."); + if (names.size() == 2) { + result = make_native_enum_type(names[1], names[0], false); + result.is_constant = false; + } + } } } return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class) { r_static = false; r_vararg = false; r_default_arg_count = 0; + if (r_native_class) { + *r_native_class = StringName(); + } StringName function_name = p_function; bool was_enum = false; @@ -4477,6 +4591,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (valid && Engine::get_singleton()->has_singleton(base_native)) { r_static = true; } +#ifdef DEBUG_ENABLED + MethodBind *native_method = ClassDB::get_method(base_native, function_name); + if (native_method && r_native_class) { + *r_native_class = native_method->get_instance_class(); + } +#endif return valid; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 75d52509a4..a4c84db6b9 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -117,7 +117,7 @@ class GDScriptAnalyzer { static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class = nullptr); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 12c10642ec..4e7d278aab 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -800,6 +800,15 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } + } else if (p_annotation->name == SNAME("@rpc")) { + if (p_argument == 0 || p_argument == 1 || p_argument == 2) { + static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" }; + for (int i = 0; i < 7; i++) { + ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + option.insert_text = option.display.quote(p_quote_style); + r_result.insert(option.display, option); + } + } } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 5f65e608b3..223a44127f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -109,7 +109,7 @@ GDScriptParser::GDScriptParser() { // Warning annotations. register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. - register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0), true); #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); @@ -158,14 +158,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ return; } - if (ignored_warning_codes.has(p_code)) { + if (ignored_warnings.has(p_code)) { return; } - String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); - if (ignored_warnings.has(warn_name)) { - return; - } int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code)); if (!warn_level) { return; @@ -180,7 +176,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.rightmost_column = p_source->rightmost_column; if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { - push_error(warning.get_message(), p_source); + push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source); return; } @@ -1837,10 +1833,18 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { if (match(GDScriptTokenizer::Token::ELIF)) { SuiteNode *else_block = alloc_node<SuiteNode>(); + else_block->parent_function = current_function; + else_block->parent_block = current_suite; + + SuiteNode *previous_suite = current_suite; + current_suite = else_block; + IfNode *elif = parse_if("elif"); else_block->statements.push_back(elif); complete_extents(else_block); n_if->false_block = else_block; + + current_suite = previous_suite; } else if (match(GDScriptTokenizer::Token::ELSE)) { consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)"); n_if->false_block = parse_suite(R"("else" block)"); @@ -3548,7 +3552,11 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con return empty; } -bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const { +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { + if (is_applied) { + return true; + } + is_applied = true; return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); } @@ -3611,6 +3619,10 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); + if (head && !ClassDB::is_parent_class(head->get_datatype().native_type, SNAME("Node"))) { + push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); + } + VariableNode *variable = static_cast<VariableNode *>(p_node); if (variable->onready) { push_error(R"("@onready" annotation can only be used once per variable.)"); @@ -3909,26 +3921,46 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); return false; } + + unsigned char locality_args = 0; + unsigned char permission_args = 0; + unsigned char transfer_mode_args = 0; + for (int i = last; i >= 0; i--) { - String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "any_peer") { - rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER; - } else if (mode == "authority") { - rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; - } else if (mode == "call_local") { + String arg = p_annotation->resolved_arguments[i].operator String(); + if (arg == "call_local") { + locality_args++; rpc_config["call_local"] = true; - } else if (mode == "call_remote") { + } else if (arg == "call_remote") { + locality_args++; rpc_config["call_local"] = false; - } else if (mode == "reliable") { + } else if (arg == "any_peer") { + permission_args++; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER; + } else if (arg == "authority") { + permission_args++; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; + } else if (arg == "reliable") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE; - } else if (mode == "unreliable") { + } else if (arg == "unreliable") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; - } else if (mode == "unreliable_ordered") { + } else if (arg == "unreliable_ordered") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; } else { - push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); + push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)", p_annotation); } } + + if (locality_args > 1) { + push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation); + } else if (permission_args > 1) { + push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation); + } else if (transfer_mode_args > 1) { + push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation); + } } function->rpc_config = rpc_config; return true; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index bc0fe58fa7..0ba0d5b6da 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -299,7 +299,9 @@ public: int leftmost_column = 0, rightmost_column = 0; Node *next = nullptr; List<AnnotationNode *> annotations; - Vector<uint32_t> ignored_warnings; +#ifdef DEBUG_ENABLED + Vector<GDScriptWarning::Code> ignored_warnings; +#endif DataType datatype; @@ -331,8 +333,10 @@ public: AnnotationInfo *info = nullptr; PropertyInfo export_info; + bool is_resolved = false; + bool is_applied = false; - bool apply(GDScriptParser *p_this, Node *p_target) const; + bool apply(GDScriptParser *p_this, Node *p_target); bool applies_to(uint32_t p_target_kinds) const; AnnotationNode() { @@ -1265,8 +1269,7 @@ private: #ifdef DEBUG_ENABLED bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; - HashSet<String> ignored_warnings; - HashSet<uint32_t> ignored_warning_codes; + HashSet<GDScriptWarning::Code> ignored_warnings; HashSet<int> unsafe_lines; #endif diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 9436146bed..ef59a07f1a 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -170,6 +170,21 @@ String GDScriptWarning::get_message() const { case RENAMED_IN_GD4_HINT: { break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. } + case INFERENCE_ON_VARIANT: { + CHECK_SYMBOLS(1); + return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]); + } + case NATIVE_METHOD_OVERRIDE: { + CHECK_SYMBOLS(2); + return vformat(R"(The method "%s" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)", symbols[0], symbols[1]); + } + case GET_NODE_DEFAULT_WITHOUT_ONREADY: { + CHECK_SYMBOLS(1); + return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]); + } + case ONREADY_WITH_EXPORT: { + return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)"; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -179,14 +194,8 @@ String GDScriptWarning::get_message() const { } int GDScriptWarning::get_default_value(Code p_code) { - if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) { - return WarnLevel::IGNORE; - } - // Too spammy by default on common cases (connect, Tween, etc.). - if (p_code == RETURN_VALUE_DISCARDED) { - return WarnLevel::IGNORE; - } - return WarnLevel::WARN; + ERR_FAIL_INDEX_V_MSG(p_code, WARNING_MAX, WarnLevel::IGNORE, "Getting default value of invalid warning code."); + return default_warning_levels[p_code]; } PropertyInfo GDScriptWarning::get_property_info(Code p_code) { @@ -240,7 +249,11 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "INT_AS_ENUM_WITHOUT_MATCH", "STATIC_CALLED_ON_INSTANCE", "CONFUSABLE_IDENTIFIER", - "RENAMED_IN_GODOT_4_HINT" + "RENAMED_IN_GODOT_4_HINT", + "INFERENCE_ON_VARIANT", + "NATIVE_METHOD_OVERRIDE", + "GET_NODE_DEFAULT_WITHOUT_ONREADY", + "ONREADY_WITH_EXPORT", }; 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 fa2907cdae..f0123c518c 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -82,9 +82,58 @@ public: STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4 + INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant. + 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. WARNING_MAX, }; + constexpr static WarnLevel default_warning_levels[] = { + WARN, // UNASSIGNED_VARIABLE + WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN + WARN, // UNUSED_VARIABLE + WARN, // UNUSED_LOCAL_CONSTANT + WARN, // SHADOWED_VARIABLE + WARN, // SHADOWED_VARIABLE_BASE_CLASS + WARN, // UNUSED_PRIVATE_CLASS_VARIABLE + WARN, // UNUSED_PARAMETER + WARN, // UNREACHABLE_CODE + WARN, // UNREACHABLE_PATTERN + WARN, // STANDALONE_EXPRESSION + WARN, // NARROWING_CONVERSION + WARN, // INCOMPATIBLE_TERNARY + WARN, // UNUSED_SIGNAL + IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.). + WARN, // PROPERTY_USED_AS_FUNCTION + WARN, // CONSTANT_USED_AS_FUNCTION + WARN, // FUNCTION_USED_AS_PROPERTY + WARN, // INTEGER_DIVISION + IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. + IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. + IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. + IGNORE, // UNSAFE_CALL_ARGUMENT // Too common in untyped scenarios. + WARN, // UNSAFE_VOID_RETURN + WARN, // DEPRECATED_KEYWORD + WARN, // STANDALONE_TERNARY + WARN, // ASSERT_ALWAYS_TRUE + WARN, // ASSERT_ALWAYS_FALSE + WARN, // REDUNDANT_AWAIT + WARN, // EMPTY_FILE + WARN, // SHADOWED_GLOBAL_IDENTIFIER + WARN, // INT_AS_ENUM_WITHOUT_CAST + WARN, // INT_AS_ENUM_WITHOUT_MATCH + WARN, // STATIC_CALLED_ON_INSTANCE + WARN, // CONFUSABLE_IDENTIFIER + WARN, // RENAMED_IN_GD4_HINT + ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type. + 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. + }; + + static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings."); + Code code = WARNING_MAX; int start_line = -1, end_line = -1; int leftmost_column = -1, rightmost_column = -1; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 5b8af0ff34..57405aa1ce 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -146,11 +146,11 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l init_language(p_source_dir); } #ifdef DEBUG_ENABLED - // Enable all warnings for GDScript, so we can test them. + // Set all warning levels to "Warn" in order to test them properly, even the ones that default to error. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { - String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); - ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); + String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i); + ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN); } #endif diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index c70a1df10d..508e46742f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". +The function signature doesn't match the parent. Parent signature is "my_function(int = <default>) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd new file mode 100644 index 0000000000..91f5071fa9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd @@ -0,0 +1,5 @@ +extends RefCounted + +func test(): + var nope := $Node + print("Cannot use $ without a Node base") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out new file mode 100644 index 0000000000..33365908bf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd new file mode 100644 index 0000000000..e781315266 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd @@ -0,0 +1,6 @@ +extends RefCounted + +@onready var nope := 0 + +func test(): + print("Cannot use @onready without a Node base") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out new file mode 100644 index 0000000000..8088d28329 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@onready" can only be used in classes that inherit "Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd new file mode 100644 index 0000000000..fac0e8756c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd @@ -0,0 +1,6 @@ +func test(): + var left_hard_int := 1 + var right_weak_int = 2 + var result_hm_int := left_hard_int if true else right_weak_int + + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out new file mode 100644 index 0000000000..71d1e2f8ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "result_hm_int" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd new file mode 100644 index 0000000000..a9004a346b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd @@ -0,0 +1,18 @@ +extends Node + +@onready var shorthand = $Node +@onready var call = get_node(^"Node") +@onready var shorthand_with_cast = $Node as Node +@onready var call_with_cast = get_node(^"Node") as Node + +func _init(): + var node := Node.new() + node.name = "Node" + add_child(node) + +func test(): + # Those are expected to be `null` since `_ready()` is never called on tests. + prints("shorthand", shorthand) + prints("call", call) + prints("shorthand_with_cast", shorthand_with_cast) + prints("call_with_cast", call_with_cast) diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out new file mode 100644 index 0000000000..eddc2deec0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out @@ -0,0 +1,5 @@ +GDTEST_OK +shorthand <null> +call <null> +shorthand_with_cast <null> +call_with_cast <null> diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd new file mode 100644 index 0000000000..02120db868 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/72501 +extends Node + +func test(): + prints("before", process_mode) + process_mode = PROCESS_MODE_PAUSABLE + prints("after", process_mode) + + var node := Node.new() + add_child(node) + prints("before", node.process_mode) + node.process_mode = PROCESS_MODE_PAUSABLE + prints("after", node.process_mode) diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out new file mode 100644 index 0000000000..1eb045a4e4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out @@ -0,0 +1,5 @@ +GDTEST_OK +before 0 +after 1 +before 0 +after 1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index 48a804ff54..b447180ea8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -2,16 +2,18 @@ func variant() -> Variant: return null var member_weak = variant() var member_typed: Variant = variant() +@warning_ignore("inference_on_variant") var member_inferred := variant() func param_weak(param = variant()) -> void: print(param) func param_typed(param: Variant = variant()) -> void: print(param) +@warning_ignore("inference_on_variant") func param_inferred(param := variant()) -> void: print(param) func return_untyped(): return variant() func return_typed() -> Variant: return variant() -@warning_ignore("unused_variable") +@warning_ignore("unused_variable", "inference_on_variant") func test() -> void: var weak = variant() var typed: Variant = variant() diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd new file mode 100644 index 0000000000..44ca5f4dd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd @@ -0,0 +1,15 @@ +func test(): + var left_hard_int := 1 + var right_hard_int := 2 + var result_hard_int := left_hard_int if true else right_hard_int + assert(result_hard_int == 1) + + @warning_ignore("inference_on_variant") + var left_hard_variant := 1 as Variant + @warning_ignore("inference_on_variant") + var right_hard_variant := 2.0 as Variant + @warning_ignore("inference_on_variant") + var result_hard_variant := left_hard_variant if true else right_hard_variant + assert(result_hard_variant == 1) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd new file mode 100644 index 0000000000..849df0921e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -0,0 +1,17 @@ +extends Node + +var add_node = do_add_node() # Hack to have one node on init and not fail at runtime. + +var shorthand = $Node +var with_self = self.get_node(^"Node") +var without_self = get_node(^"Node") +var with_cast = get_node(^"Node") as Node +var shorthand_with_cast = $Node as Node + +func test(): + print("warn") + +func do_add_node(): + var node = Node.new() + node.name = "Node" + add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out new file mode 100644 index 0000000000..62b3ae291f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out @@ -0,0 +1,22 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 6 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 7 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 8 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 9 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd new file mode 100644 index 0000000000..024e91b7c6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd @@ -0,0 +1,6 @@ +func test(): + var inferred_with_variant := return_variant() + print(inferred_with_variant) + +func return_variant() -> Variant: + return "warn" diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out new file mode 100644 index 0000000000..1d4078d2f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> INFERENCE_ON_VARIANT +>> The variable type is being inferred from a Variant value, so it will be typed as Variant. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd new file mode 100644 index 0000000000..0b358ca5f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd @@ -0,0 +1,6 @@ +extends Node + +@onready @export var conflict = "" + +func test(): + print("warn") diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out new file mode 100644 index 0000000000..ff184f9f04 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ONREADY_WITH_EXPORT +>> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd new file mode 100644 index 0000000000..19d40f8ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd @@ -0,0 +1,5 @@ +func test(): + print("warn") + +func get(_property: StringName) -> Variant: + return null diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out new file mode 100644 index 0000000000..793faa05d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> NATIVE_METHOD_OVERRIDE +>> The method "get" overrides a method from native class "Object". This won't be called by the engine and may not work as expected. +warn diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 1a09b5bdcc..bd3ee1881f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -54,6 +54,9 @@ #include "modules/modules_enabled.gen.h" // For csg, gridmap. +#ifdef TOOLS_ENABLED +#include "editor/editor_file_system.h" +#endif #ifdef MODULE_CSG_ENABLED #include "modules/csg/csg_shape.h" #endif // MODULE_CSG_ENABLED @@ -3232,54 +3235,38 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p p_state->source_images.push_back(Ref<Image>()); } else { Error err = OK; - bool must_import = false; + bool must_import = true; + Vector<uint8_t> img_data = img->get_data(); + Dictionary generator_parameters; String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png"; - if (!FileAccess::exists(file_path + ".import")) { + if (FileAccess::exists(file_path + ".import")) { Ref<ConfigFile> config; config.instantiate(); - config->set_value("remap", "importer", "texture"); - config->set_value("remap", "type", "Texture2D"); - // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again. - if (!config->has_section_key("params", "mipmaps/generate")) { - config->set_value("params", "mipmaps/generate", true); + config->load(file_path + ".import"); + if (config->has_section_key("remap", "generator_parameters")) { + generator_parameters = (Dictionary)config->get_value("remap", "generator_parameters"); } - - if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) { - //use defaults if exist - Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture"); - List<Variant> importer_def_keys; - importer_defaults.get_key_list(&importer_def_keys); - for (const Variant &key : importer_def_keys) { - if (!config->has_section_key("params", (String)key)) { - config->set_value("params", (String)key, importer_defaults[key]); - } - } + if (!generator_parameters.has("md5")) { + must_import = false; // Didn't come form a gltf document; don't overwrite. } - err = config->save(file_path + ".import"); - ERR_FAIL_COND_V(err != OK, err); - must_import = true; - } - Vector<uint8_t> png_buffer = img->save_png_to_buffer(); - if (ResourceLoader::exists(file_path)) { - Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::READ, &err); - if (err == OK && file.is_valid()) { - Vector<uint8_t> orig_png_buffer = file->get_buffer(file->get_length()); - if (png_buffer != orig_png_buffer) { - must_import = true; - } + String existing_md5 = generator_parameters["md5"]; + unsigned char md5_hash[16]; + CryptoCore::md5(img_data.ptr(), img_data.size(), md5_hash); + String new_md5 = String::hex_encode_buffer(md5_hash, 16); + generator_parameters["md5"] = new_md5; + if (new_md5 == existing_md5) { + must_import = false; } - } else { - must_import = true; } if (must_import) { - Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err); + err = img->save_png(file_path); ERR_FAIL_COND_V(err != OK, err); - ERR_FAIL_COND_V(file.is_null(), FAILED); - file->store_buffer(png_buffer); - file->flush(); - file.unref(); // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed. - ResourceLoader::import(file_path); + HashMap<StringName, Variant> custom_options; + custom_options[SNAME("mipmaps/generate")] = true; + // Will only use project settings defaults if custom_importer is empty. + EditorFileSystem::get_singleton()->update_file(file_path); + EditorFileSystem::get_singleton()->reimport_append(file_path, custom_options, String(), generator_parameters); } Ref<Texture2D> saved_image = ResourceLoader::load(file_path, "Texture2D"); if (saved_image.is_valid()) { diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs index 02d0226e90..93baf4e51c 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -30,7 +30,7 @@ namespace GodotPlugins if (baseDirectory != null) { if (!Path.EndsInDirectorySeparator(baseDirectory)) - baseDirectory += Path.PathSeparator; + baseDirectory += Path.DirectorySeparatorChar; // This SetData call effectively sets AppContext.BaseDirectory // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25 AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 9b474bf2ce..b55188ce0c 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -6118,20 +6118,22 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS Vector<UChar *> skeletons; skeletons.resize(p_dict.size()); - USpoofChecker *sc = uspoof_open(&status); - uspoof_setChecks(sc, USPOOF_CONFUSABLE, &status); + if (sc_conf == nullptr) { + sc_conf = uspoof_open(&status); + uspoof_setChecks(sc_conf, USPOOF_CONFUSABLE, &status); + } for (int i = 0; i < p_dict.size(); i++) { Char16String word = p_dict[i].utf16(); - int32_t len = uspoof_getSkeleton(sc, 0, word.get_data(), -1, NULL, 0, &status); + int32_t len = uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, NULL, 0, &status); skeletons.write[i] = (UChar *)memalloc(++len * sizeof(UChar)); status = U_ZERO_ERROR; - uspoof_getSkeleton(sc, 0, word.get_data(), -1, skeletons.write[i], len, &status); + uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, skeletons.write[i], len, &status); } - int32_t len = uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, NULL, 0, &status); + int32_t len = uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, NULL, 0, &status); UChar *skel = (UChar *)memalloc(++len * sizeof(UChar)); status = U_ZERO_ERROR; - uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, skel, len, &status); + uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, skel, len, &status); for (int i = 0; i < skeletons.size(); i++) { if (u_strcmp(skel, skeletons[i]) == 0) { match_index = i; @@ -6143,7 +6145,6 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS for (int i = 0; i < skeletons.size(); i++) { memfree(skeletons.write[i]); } - uspoof_close(sc); ERR_FAIL_COND_V_MSG(U_FAILURE(status), -1, u_errorName(status)); @@ -6159,19 +6160,18 @@ bool TextServerAdvanced::_spoof_check(const String &p_string) const { UErrorCode status = U_ZERO_ERROR; Char16String utf16 = p_string.utf16(); - USet *allowed = uset_openEmpty(); - uset_addAll(allowed, uspoof_getRecommendedSet(&status)); - uset_addAll(allowed, uspoof_getInclusionSet(&status)); - - USpoofChecker *sc = uspoof_open(&status); - uspoof_setAllowedChars(sc, allowed, &status); - uspoof_setRestrictionLevel(sc, USPOOF_MODERATELY_RESTRICTIVE); - - int32_t bitmask = uspoof_check(sc, utf16.get_data(), -1, NULL, &status); - - uspoof_close(sc); - uset_close(allowed); + if (allowed == nullptr) { + allowed = uset_openEmpty(); + uset_addAll(allowed, uspoof_getRecommendedSet(&status)); + uset_addAll(allowed, uspoof_getInclusionSet(&status)); + } + if (sc_spoof == nullptr) { + sc_spoof = uspoof_open(&status); + uspoof_setAllowedChars(sc_spoof, allowed, &status); + uspoof_setRestrictionLevel(sc_spoof, USPOOF_MODERATELY_RESTRICTIVE); + } + int32_t bitmask = uspoof_check(sc_spoof, utf16.get_data(), -1, NULL, &status); ERR_FAIL_COND_V_MSG(U_FAILURE(status), false, u_errorName(status)); return (bitmask != 0); @@ -6587,5 +6587,17 @@ TextServerAdvanced::~TextServerAdvanced() { FT_Done_FreeType(ft_library); } #endif + if (sc_spoof != nullptr) { + uspoof_close(sc_spoof); + sc_spoof = nullptr; + } + if (sc_conf != nullptr) { + uspoof_close(sc_conf); + sc_conf = nullptr; + } + if (allowed != nullptr) { + uset_close(allowed); + allowed = nullptr; + } u_cleanup(); } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index c7fe46d554..1acf5b21f0 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -159,6 +159,9 @@ class TextServerAdvanced : public TextServerExtension { // ICU support data. bool icu_data_loaded = false; + mutable USet *allowed = nullptr; + mutable USpoofChecker *sc_spoof = nullptr; + mutable USpoofChecker *sc_conf = nullptr; // Font cache data. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index c02acbee83..8cc2b1eb97 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -252,6 +252,7 @@ static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets"; static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets"; static const int DEFAULT_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' +static const int VULKAN_MIN_SDK_VERSION = 24; static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' #ifndef ANDROID_ENABLED @@ -1056,6 +1057,15 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<bool> feature_required_list; Vector<int> feature_versions; + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (has_vulkan) { + // Require vulkan hardware level 1 support + feature_names.push_back("android.hardware.vulkan.level"); + feature_required_list.push_back(true); + feature_versions.push_back(1); + } + if (feature_names.size() > 0) { ofs += 24; // skip over end tag @@ -2373,6 +2383,19 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit err += "\n"; } + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool uses_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (current_renderer == "forward_plus") { + // Warning only, so don't override `valid`. + err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer); + err += "\n"; + } + if (uses_vulkan && min_sdk_int < VULKAN_MIN_SDK_VERSION) { + // Warning only, so don't override `valid`. + err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer); + err += "\n"; + } + r_error = err; return valid; } diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 5e71116c10..7eb595f48d 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -273,6 +273,12 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"com.oculus.feature.PASSTHROUGH\" android:required=\"true\" />\n"; } } + + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (has_vulkan) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"true\" android:version=\"1\" />\n"; + } return manifest_xr_features; } diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index 7efac4ce71..f76f597140 100644 --- a/platform/android/java/lib/res/values/strings.xml +++ b/platform/android/java/lib/res/values/strings.xml @@ -14,6 +14,7 @@ <string name="text_button_cancel_verify">Cancel Verification</string> <string name="text_error_title">Error!</string> <string name="error_engine_setup_message">Unable to setup the Godot Engine! Aborting…</string> + <string name="error_missing_vulkan_requirements_message">This device does not meet the requirements for Vulkan support! Aborting…</string> <!-- APK Expansion Strings --> diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 50263bc392..6296ee2c22 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -258,13 +258,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC */ @Keep private boolean onVideoInit() { - final Activity activity = getActivity(); + final Activity activity = requireActivity(); containerLayout = new FrameLayout(activity); containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // GodotEditText layout GodotEditText editText = new GodotEditText(activity); - editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, + editText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, (int)getResources().getDimension(R.dimen.text_edit_height))); // ...add to FrameLayout containerLayout.addView(editText); @@ -279,6 +279,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC if (renderer.equals("gl_compatibility")) { mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl); } else { + if (!meetsVulkanRequirements(activity.getPackageManager())) { + Log.e(TAG, "Missing requirements for vulkan support! Aborting..."); + alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit); + return false; + } mRenderView = new GodotVulkanRenderView(activity, this); } @@ -317,6 +322,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC return true; } + /** + * Returns true if the device meets the base requirements for Vulkan support, false otherwise. + */ + private boolean meetsVulkanRequirements(@Nullable PackageManager packageManager) { + if (packageManager == null) { + return false; + } + + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1); + } + public void setKeepScreenOn(final boolean p_enabled) { runOnUiThread(() -> { if (p_enabled) { diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 8377e81a53..493e4ad20f 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4109,7 +4109,7 @@ void DisplayServerX11::process_events() { case FocusIn: { DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); - if (ime_window_event) { + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { break; } @@ -4157,7 +4157,7 @@ void DisplayServerX11::process_events() { case FocusOut: { DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); WindowData &wd = windows[window_id]; - if (wd.ime_active && event.xfocus.detail == NotifyInferior) { + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { break; } if (wd.ime_active) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index f09e4962a9..a930b8d972 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -644,8 +644,10 @@ Rect2 Control::get_parent_anchorable_rect() const { parent_rect = data.parent_canvas_item->get_anchorable_rect(); } else { #ifdef TOOLS_ENABLED - Node *edited_root = get_tree()->get_edited_scene_root(); - if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) { + Node *edited_scene_root = get_tree()->get_edited_scene_root(); + Node *scene_root_parent = edited_scene_root ? edited_scene_root->get_parent() : nullptr; + + if (scene_root_parent && get_viewport() == scene_root_parent->get_viewport()) { parent_rect.size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); } else { parent_rect = get_viewport()->get_visible_rect(); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 0ea8f6c5f1..906b478eb3 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -491,6 +491,17 @@ int CanvasItem::get_z_index() const { return z_index; } +int CanvasItem::get_effective_z_index() const { + int effective_z_index = z_index; + if (is_z_relative()) { + CanvasItem *p = get_parent_item(); + if (p) { + effective_z_index += p->get_effective_z_index(); + } + } + return effective_z_index; +} + void CanvasItem::set_y_sort_enabled(bool p_enabled) { y_sort_enabled = p_enabled; RS::get_singleton()->canvas_item_set_sort_children_by_y(canvas_item, y_sort_enabled); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 2fa1d56667..5fbf043159 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -246,6 +246,7 @@ public: void set_z_index(int p_z); int get_z_index() const; + int get_effective_z_index() const; void set_z_as_relative(bool p_enabled); bool is_z_relative() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 28521c5bbe..7091dd0388 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -36,6 +36,7 @@ #include "core/object/message_queue.h" #include "core/string/translation.h" #include "core/templates/pair.h" +#include "core/templates/sort_array.h" #include "scene/2d/audio_listener_2d.h" #include "scene/2d/camera_2d.h" #include "scene/2d/collision_object_2d.h" @@ -669,6 +670,25 @@ void Viewport::_process_picking() { point_params.pick_point = true; int rc = ss2d->intersect_point(point_params, res, 64); + if (physics_object_picking_sort) { + struct ComparatorCollisionObjects { + bool operator()(const PhysicsDirectSpaceState2D::ShapeResult &p_a, const PhysicsDirectSpaceState2D::ShapeResult &p_b) const { + CollisionObject2D *a = Object::cast_to<CollisionObject2D>(p_a.collider); + CollisionObject2D *b = Object::cast_to<CollisionObject2D>(p_b.collider); + if (!a || !b) { + return false; + } + int za = a->get_effective_z_index(); + int zb = b->get_effective_z_index(); + if (za != zb) { + return zb < za; + } + return a->is_greater_than(b); + } + }; + SortArray<PhysicsDirectSpaceState2D::ShapeResult, ComparatorCollisionObjects> sorter; + sorter.sort(res, rc); + } for (int i = 0; i < rc; i++) { if (res[i].collider_id.is_valid() && res[i].collider) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider); @@ -2864,6 +2884,14 @@ bool Viewport::get_physics_object_picking() { return physics_object_picking; } +void Viewport::set_physics_object_picking_sort(bool p_enable) { + physics_object_picking_sort = p_enable; +} + +bool Viewport::get_physics_object_picking_sort() { + return physics_object_picking_sort; +} + Vector2 Viewport::get_camera_coords(const Vector2 &p_viewport_coords) const { Transform2D xf = stretch_transform * global_canvas_transform; return xf.xform(p_viewport_coords); @@ -3798,6 +3826,8 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_object_picking", "enable"), &Viewport::set_physics_object_picking); ClassDB::bind_method(D_METHOD("get_physics_object_picking"), &Viewport::get_physics_object_picking); + ClassDB::bind_method(D_METHOD("set_physics_object_picking_sort", "enable"), &Viewport::set_physics_object_picking_sort); + ClassDB::bind_method(D_METHOD("get_physics_object_picking_sort"), &Viewport::get_physics_object_picking_sort); ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid); ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input); @@ -3949,6 +3979,7 @@ void Viewport::_bind_methods() { #endif ADD_GROUP("Physics", "physics_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking"), "set_physics_object_picking", "get_physics_object_picking"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking_sort"), "set_physics_object_picking_sort", "get_physics_object_picking_sort"); ADD_GROUP("GUI", "gui_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_disable_input"), "set_disable_input", "is_input_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_snap_controls_to_pixels"), "set_snap_controls_to_pixels", "is_snap_controls_to_pixels_enabled"); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 2142aaaaef..4144eaabb9 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -246,6 +246,7 @@ private: bool snap_2d_vertices_to_pixel = false; bool physics_object_picking = false; + bool physics_object_picking_sort = false; List<Ref<InputEvent>> physics_picking_events; ObjectID physics_object_capture; ObjectID physics_object_over; @@ -574,6 +575,8 @@ public: void set_physics_object_picking(bool p_enable); bool get_physics_object_picking(); + void set_physics_object_picking_sort(bool p_enable); + bool get_physics_object_picking_sort(); Variant gui_get_drag_data() const; diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index e5a1adff20..0f7985bee6 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -2712,6 +2712,9 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { for (const StringName &E : theme_types) { if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f == this) { + continue; + } if (f.is_valid()) { theme_font = f; theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -2729,6 +2732,9 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { for (const StringName &E : theme_types) { if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f == this) { + continue; + } if (f.is_valid()) { theme_font = f; theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -2739,11 +2745,13 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { // If they don't exist, use any type to return the default/empty value. Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); - if (f.is_valid()) { - theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + if (f != this) { + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + } + return f; } - return f; } return Ref<Font>(); @@ -3061,6 +3069,9 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { for (const StringName &E : theme_types) { if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f == this) { + continue; + } if (f.is_valid()) { theme_font = f; theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -3078,6 +3089,9 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { for (const StringName &E : theme_types) { if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f == this) { + continue; + } if (f.is_valid()) { theme_font = f; theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -3088,11 +3102,13 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { // If they don't exist, use any type to return the default/empty value. Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); - if (f.is_valid()) { - theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + if (f != this) { + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + } + return f; } - return f; } return Ref<Font>(); diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index bd8c11186e..f102bc0650 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -2634,6 +2634,7 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() { // Default CanvasGroup shader. shader_type canvas_item; +render_mode unshaded; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; @@ -2661,6 +2662,7 @@ void fragment() { // Default clip children shader. shader_type canvas_item; +render_mode unshaded; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; |