summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/SCsub3
-rw-r--r--modules/basis_universal/register_types.cpp2
-rw-r--r--modules/csg/csg_shape.cpp6
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp8
-rw-r--r--modules/enet/enet_packet_peer.cpp2
-rw-r--r--modules/etcpak/SCsub1
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp72
-rw-r--r--modules/etcpak/image_compress_etcpak.h2
-rw-r--r--modules/gdscript/README.md139
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp49
-rw-r--r--modules/gdscript/editor/gdscript_docgen.h5
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp134
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h11
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp104
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h6
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd5
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd5
-rw-r--r--modules/gdscript/gdscript.cpp263
-rw-r--r--modules/gdscript/gdscript.h53
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp90
-rw-r--r--modules/gdscript/gdscript_analyzer.h3
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_cache.cpp55
-rw-r--r--modules/gdscript/gdscript_cache.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp38
-rw-r--r--modules/gdscript/gdscript_compiler.h20
-rw-r--r--modules/gdscript/gdscript_editor.cpp397
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp32
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h14
-rw-r--r--modules/gdscript/gdscript_parser.cpp155
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp83
-rw-r--r--modules/gdscript/gdscript_tokenizer.h58
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp493
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.h93
-rw-r--r--modules/gdscript/gdscript_utility_callable.cpp111
-rw-r--r--modules/gdscript/gdscript_utility_callable.h65
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp7
-rw-r--r--modules/gdscript/gdscript_vm.cpp47
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp24
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp8
-rw-r--r--modules/gdscript/register_types.cpp27
-rw-r--r--modules/gdscript/tests/README.md41
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp98
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h13
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out2
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg4
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd6
-rw-r--r--modules/gdscript/tests/scripts/lsp/class.gd (renamed from modules/gdscript/tests/scripts/lsp/class.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/enums.gd (renamed from modules/gdscript/tests/scripts/lsp/enums.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/indentation.gd (renamed from modules/gdscript/tests/scripts/lsp/indentation.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/lambdas.gd (renamed from modules/gdscript/tests/scripts/lsp/lambdas.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/local_variables.gd (renamed from modules/gdscript/tests/scripts/lsp/local_variables.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/properties.gd (renamed from modules/gdscript/tests/scripts/lsp/properties.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/scopes.gd (renamed from modules/gdscript/tests/scripts/lsp/scopes.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd (renamed from modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd)0
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out)0
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_if.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.out1
-rw-r--r--modules/gdscript/tests/scripts/project.godot2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/free_is_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out7
-rw-r--r--modules/gdscript/tests/test_completion.h203
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp61
-rw-r--r--modules/gdscript/tests/test_gdscript.h1
-rw-r--r--modules/gdscript/tests/test_lsp.h18
-rw-r--r--modules/glslang/SCsub1
-rw-r--r--modules/glslang/config.py4
-rw-r--r--modules/glslang/register_types.cpp14
-rw-r--r--modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml5
-rw-r--r--modules/gltf/doc_classes/GLTFBufferView.xml18
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml19
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsShape.xml7
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml8
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp68
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.h2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp81
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.h13
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp176
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.h64
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp171
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h4
-rw-r--r--modules/gltf/editor/editor_scene_importer_fbx.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.h2
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp1
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h2
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp316
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp300
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.h34
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.cpp86
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.h5
-rw-r--r--modules/gltf/gltf_document.cpp446
-rw-r--r--modules/gltf/gltf_document.h3
-rw-r--r--modules/gltf/gltf_state.cpp28
-rw-r--r--modules/gltf/gltf_state.h1
-rw-r--r--modules/gltf/register_types.cpp47
-rw-r--r--modules/gltf/structures/gltf_buffer_view.compat.inc61
-rw-r--r--modules/gltf/structures/gltf_buffer_view.cpp25
-rw-r--r--modules/gltf/structures/gltf_buffer_view.h22
-rw-r--r--modules/gltf/structures/gltf_node.cpp16
-rw-r--r--modules/gltf/structures/gltf_node.h6
-rw-r--r--modules/gltf/structures/gltf_skeleton.h14
-rw-r--r--modules/gltf/structures/gltf_skin.h1
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp3
-rw-r--r--modules/gridmap/grid_map.cpp4
-rw-r--r--modules/jpg/image_loader_jpegd.cpp13
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp88
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl15
-rw-r--r--modules/lightmapper_rd/register_types.cpp22
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp29
-rw-r--r--modules/minimp3/audio_stream_mp3.h7
-rw-r--r--modules/minimp3/resource_importer_mp3.cpp2
-rw-r--r--modules/mono/SCsub6
-rw-r--r--modules/mono/csharp_script.cpp130
-rw-r--r--modules/mono/csharp_script.h95
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs654
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs59
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs20
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs20
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs22
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs653
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs27
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj23
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs623
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorOutOfSyncWarning.cs37
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs57
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs19
-rw-r--r--modules/mono/editor/bindings_generator.cpp16
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp6
-rw-r--r--modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs9
-rw-r--r--modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs9
-rw-r--r--modules/mono/glue/GodotSharp/.editorconfig3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs285
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs30
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs75
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs25
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs23
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs23
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs23
-rw-r--r--modules/mono/glue/runtime_interop.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp8
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h4
-rw-r--r--modules/mono/utils/path_utils.cpp2
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp2
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp2
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp6
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp4
-rw-r--r--modules/multiplayer/multiplayer_spawner.h2
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp6
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.h2
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp164
-rw-r--r--modules/multiplayer/scene_cache_interface.h27
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp4
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp16
-rw-r--r--modules/multiplayer/scene_replication_interface.h4
-rw-r--r--modules/navigation/godot_navigation_server.cpp18
-rw-r--r--modules/navigation/godot_navigation_server.h4
-rw-r--r--modules/navigation/godot_navigation_server_2d.cpp4
-rw-r--r--modules/navigation/godot_navigation_server_2d.h1
-rw-r--r--modules/navigation/nav_agent.cpp4
-rw-r--r--modules/navigation/nav_map.cpp57
-rw-r--r--modules/navigation/nav_map.h15
-rw-r--r--modules/navigation/nav_mesh_generator_2d.cpp55
-rw-r--r--modules/navigation/nav_mesh_generator_2d.h1
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp31
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h1
-rw-r--r--modules/navigation/nav_utils.h2
-rw-r--r--modules/noise/editor/noise_editor_plugin.cpp2
-rw-r--r--modules/noise/noise_texture_2d.cpp8
-rw-r--r--modules/noise/noise_texture_3d.cpp8
-rw-r--r--modules/openxr/SCsub35
-rw-r--r--modules/openxr/action_map/SCsub10
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile_metadata.cpp3
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml38
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml14
-rw-r--r--modules/openxr/doc_classes/OpenXRHand.xml31
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml22
-rw-r--r--modules/openxr/editor/openxr_action_map_editor.cpp5
-rw-r--r--modules/openxr/editor/openxr_editor_plugin.cpp5
-rw-r--r--modules/openxr/editor/openxr_editor_plugin.h4
-rw-r--r--modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp2
-rw-r--r--modules/openxr/editor/openxr_select_runtime.cpp132
-rw-r--r--modules/openxr/editor/openxr_select_runtime.h51
-rw-r--r--modules/openxr/extensions/SCsub18
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h1
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp22
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h6
-rw-r--r--modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp246
-rw-r--r--modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h231
-rw-r--r--modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp2
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp65
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h12
-rw-r--r--modules/openxr/extensions/openxr_local_floor_extension.cpp59
-rw-r--r--modules/openxr/extensions/openxr_local_floor_extension.h53
-rw-r--r--modules/openxr/extensions/openxr_meta_controller_extension.cpp184
-rw-r--r--modules/openxr/extensions/openxr_meta_controller_extension.h55
-rw-r--r--modules/openxr/extensions/platform/openxr_android_extension.cpp (renamed from modules/openxr/extensions/openxr_android_extension.cpp)2
-rw-r--r--modules/openxr/extensions/platform/openxr_android_extension.h (renamed from modules/openxr/extensions/openxr_android_extension.h)4
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.cpp (renamed from modules/openxr/extensions/openxr_opengl_extension.cpp)14
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.h (renamed from modules/openxr/extensions/openxr_opengl_extension.h)14
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.cpp (renamed from modules/openxr/extensions/openxr_vulkan_extension.cpp)22
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.h (renamed from modules/openxr/extensions/openxr_vulkan_extension.h)26
-rw-r--r--modules/openxr/openxr_api.cpp246
-rw-r--r--modules/openxr/openxr_api.h27
-rw-r--r--modules/openxr/openxr_api_extension.cpp32
-rw-r--r--modules/openxr/openxr_api_extension.h16
-rw-r--r--modules/openxr/openxr_interface.cpp86
-rw-r--r--modules/openxr/openxr_interface.h12
-rw-r--r--modules/openxr/openxr_platform_inc.h2
-rw-r--r--modules/openxr/register_types.cpp8
-rw-r--r--modules/openxr/scene/SCsub10
-rw-r--r--modules/openxr/scene/openxr_hand.cpp247
-rw-r--r--modules/openxr/scene/openxr_hand.h35
-rw-r--r--modules/regex/regex.cpp12
-rw-r--r--modules/regex/tests/test_regex.h139
-rw-r--r--modules/svg/SCsub2
-rw-r--r--modules/svg/image_loader_svg.cpp2
-rw-r--r--modules/svg/register_types.cpp9
-rw-r--r--modules/text_server_adv/SCsub4
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct6
-rw-r--r--modules/text_server_adv/register_types.h2
-rw-r--r--modules/text_server_adv/script_iterator.h2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp51
-rw-r--r--modules/text_server_adv/text_server_adv.h13
-rw-r--r--modules/text_server_adv/thorvg_bounds_iterator.cpp2
-rw-r--r--modules/text_server_adv/thorvg_bounds_iterator.h2
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.cpp2
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.h2
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct2
-rw-r--r--modules/text_server_fb/register_types.h2
-rw-r--r--modules/text_server_fb/text_server_fb.cpp41
-rw-r--r--modules/text_server_fb/text_server_fb.h12
-rw-r--r--modules/text_server_fb/thorvg_bounds_iterator.cpp2
-rw-r--r--modules/text_server_fb/thorvg_bounds_iterator.h2
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.cpp2
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.h2
-rw-r--r--modules/upnp/upnp.cpp2
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp32
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.h7
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.cpp2
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp4
-rw-r--r--modules/webxr/webxr_interface_js.cpp1
-rw-r--r--modules/zip/zip_packer.cpp2
299 files changed, 8414 insertions, 3235 deletions
diff --git a/modules/SCsub b/modules/SCsub
index fcc01e2c1b..7c9946170f 100644
--- a/modules/SCsub
+++ b/modules/SCsub
@@ -7,6 +7,9 @@ Import("env")
env_modules = env.Clone()
+# Allow modules to detect if they are being built as a module.
+env_modules.Append(CPPDEFINES=["GODOT_MODULE"])
+
Export("env_modules")
# Header with MODULE_*_ENABLED defines.
diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp
index 7c0bc4ac82..f538fc6676 100644
--- a/modules/basis_universal/register_types.cpp
+++ b/modules/basis_universal/register_types.cpp
@@ -221,7 +221,7 @@ static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size
imgfmt = Image::FORMAT_DXT5_RA_AS_RG;
} else if (RS::get_singleton()->has_os_feature("etc2")) {
format = basist::transcoder_texture_format::cTFETC2; // get this from renderer
- imgfmt = Image::FORMAT_ETC2_RGBA8;
+ imgfmt = Image::FORMAT_ETC2_RA_AS_RG;
} else {
//opengl most likely, bad for normal maps, nothing to do about this.
format = basist::transcoder_texture_format::cTFRGBA32;
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 6082b468f7..604ad5e1e4 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -150,13 +150,13 @@ float CSGShape3D::get_snap() const {
void CSGShape3D::_make_dirty(bool p_parent_removing) {
if ((p_parent_removing || is_root_shape()) && !dirty) {
- call_deferred(SNAME("_update_shape")); // Must be deferred; otherwise, is_root_shape() will use the previous parent
+ callable_mp(this, &CSGShape3D::_update_shape).call_deferred(); // Must be deferred; otherwise, is_root_shape() will use the previous parent.
}
if (!is_root_shape()) {
parent_shape->_make_dirty();
} else if (!dirty) {
- call_deferred(SNAME("_update_shape"));
+ callable_mp(this, &CSGShape3D::_update_shape).call_deferred();
}
dirty = true;
@@ -800,7 +800,7 @@ CSGBrush *CSGMesh3D::_build_brush() {
if (arrays.size() == 0) {
_make_dirty();
- ERR_FAIL_COND_V(arrays.size() == 0, memnew(CSGBrush));
+ ERR_FAIL_COND_V(arrays.is_empty(), memnew(CSGBrush));
}
Vector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX];
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index 63f12ea1c1..910c4ed242 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -40,20 +40,20 @@ void ENetMultiplayerPeer::set_target_peer(int p_peer) {
int ENetMultiplayerPeer::get_packet_peer() const {
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
return incoming_packets.front()->get().from;
}
MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const {
ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, TRANSFER_MODE_RELIABLE);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), TRANSFER_MODE_RELIABLE);
return incoming_packets.front()->get().transfer_mode;
}
int ENetMultiplayerPeer::get_packet_channel() const {
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
int ch = incoming_packets.front()->get().channel;
if (ch >= SYSCH_MAX) { // First 2 channels are reserved.
return ch - SYSCH_MAX + 1;
@@ -321,7 +321,7 @@ int ENetMultiplayerPeer::get_available_packet_count() const {
}
Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
- ERR_FAIL_COND_V_MSG(incoming_packets.size() == 0, ERR_UNAVAILABLE, "No incoming packets available.");
+ ERR_FAIL_COND_V_MSG(incoming_packets.is_empty(), ERR_UNAVAILABLE, "No incoming packets available.");
_pop_current_packet();
diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp
index f2bf5337ee..edb33fc96b 100644
--- a/modules/enet/enet_packet_peer.cpp
+++ b/modules/enet/enet_packet_peer.cpp
@@ -90,7 +90,7 @@ int ENetPacketPeer::get_available_packet_count() const {
Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
- ERR_FAIL_COND_V(!packet_queue.size(), ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(packet_queue.is_empty(), ERR_UNAVAILABLE);
if (last_packet) {
enet_packet_destroy(last_packet);
last_packet = nullptr;
diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub
index 3a4bff8e87..2d3b69be75 100644
--- a/modules/etcpak/SCsub
+++ b/modules/etcpak/SCsub
@@ -13,7 +13,6 @@ thirdparty_dir = "#thirdparty/etcpak/"
thirdparty_sources = [
"Dither.cpp",
"ProcessDxtc.cpp",
- "ProcessRgtc.cpp",
"ProcessRGB.cpp",
"Tables.cpp",
]
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index f528b92cf2..087d3a9314 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -35,18 +35,17 @@
#include <ProcessDxtc.hpp>
#include <ProcessRGB.hpp>
-#include <ProcessRgtc.hpp>
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
switch (p_channels) {
case Image::USED_CHANNELS_L:
- return EtcpakType::ETCPAK_TYPE_ETC1;
+ return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_LA:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
case Image::USED_CHANNELS_R:
- return EtcpakType::ETCPAK_TYPE_ETC2;
+ return EtcpakType::ETCPAK_TYPE_ETC2_R;
case Image::USED_CHANNELS_RG:
- return EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG;
+ return EtcpakType::ETCPAK_TYPE_ETC2_RG;
case Image::USED_CHANNELS_RGB:
return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_RGBA:
@@ -114,6 +113,12 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
target_format = Image::FORMAT_ETC2_RGB8;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) {
+ target_format = Image::FORMAT_ETC2_R11;
+ r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
+ target_format = Image::FORMAT_ETC2_RG11;
+ r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
target_format = Image::FORMAT_ETC2_RA_AS_RG;
r_img->convert_rg_to_ra_rgba8();
@@ -224,22 +229,49 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
// Override the src_mip_read pointer to our temporary Vector.
src_mip_read = padded_src.ptr();
}
- if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
- CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
- CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
- CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
- CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
- CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
- CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w);
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
- CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w);
- } else {
- ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
+
+ switch (p_compresstype) {
+ case EtcpakType::ETCPAK_TYPE_ETC1:
+ CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2:
+ CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
+ case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
+ CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_R:
+ CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_RG:
+ CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_DXT1:
+ CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_DXT5:
+ case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
+ CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_RGTC_R:
+ CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_RGTC_RG:
+ CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ default:
+ ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
+ break;
}
}
diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h
index ff8bb635b4..9d5343740b 100644
--- a/modules/etcpak/image_compress_etcpak.h
+++ b/modules/etcpak/image_compress_etcpak.h
@@ -38,6 +38,8 @@ enum class EtcpakType {
ETCPAK_TYPE_ETC2,
ETCPAK_TYPE_ETC2_ALPHA,
ETCPAK_TYPE_ETC2_RA_AS_RG,
+ ETCPAK_TYPE_ETC2_R,
+ ETCPAK_TYPE_ETC2_RG,
ETCPAK_TYPE_DXT1,
ETCPAK_TYPE_DXT5,
ETCPAK_TYPE_DXT5_RA_AS_RG,
diff --git a/modules/gdscript/README.md b/modules/gdscript/README.md
new file mode 100644
index 0000000000..865475d37d
--- /dev/null
+++ b/modules/gdscript/README.md
@@ -0,0 +1,139 @@
+# Basic GDScript module architecture
+This provides some basic information in how GDScript is implemented and integrates with the rest of the engine. You can learn more about GDScript in the [documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/index.html). It describes the syntax and user facing systems and concepts, and can be used as a reference for what user expectations are.
+
+
+## General design
+
+GDScript is:
+
+1. A [gradually typed](https://en.wikipedia.org/wiki/Gradual_typing) language. Type hints are optional and help with static analysis and performance. However, typed code must easily interoperate with untyped code.
+2. A tightly designed language. Features are added because they are _needed_, and not because they can be added or are interesting to develop.
+3. Primarily an interpreted scripting language: it is compiled to GDScript byte code and interpreted in a GDScript virtual machine. It is meant to be easy to use and develop gameplay in. It is not meant for CPU-intensive algorithms or data processing, and is not optimized for it. For that, [C#](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html) or [GDExtension](https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html) may be used.
+
+
+## Integration into Godot
+
+GDScript is integrated into Godot as a module. Since modules are optional, this means that Godot may be built without GDScript and work perfectly fine without it!
+
+The GDScript module interfaces with Godot's codebase by inheriting from the engine's scripting-related classes. New languages inherit from [`ScriptLanguage`](/core/object/script_language.h), and are registered in Godot's [`ScriptServer`](/core/object/script_language.h). Scripts, referring to a file containing code, are represented in the engine by the `Script` class. Instances of that script, which are used at runtime when actually executing the code, inherit from [`ScriptInstance`](/core/object/script_instance.h).
+
+To access Godot's internal classes, GDScript uses [`ClassDB`](/core/object/class_db.h). `ClassDB` is where Godot registers classes, methods and properties that it wants exposed to its scripting system. This is how GDScript understands that `Node2D` is a class it can use, and that it has a `get_parent()` method.
+
+[Built-in GDScript methods](https://docs.godotengine.org/en/latest/classes/class_@gdscript.html#methods) are defined and exported by [`GDScriptUtilityFunctions`](gdscript_utility_functions.h), whereas [global scope methods](https://docs.godotengine.org/en/latest/classes/class_%2540globalscope.html) are registered in [`Variant::_register_variant_utility_functions()`](/core/variant/variant_utility.cpp).
+
+
+## Compilation
+
+Scripts can be at different stages of compilation. The process isn't entirely linear, but consists of this general order: tokenizing, parsing, analyzing, and finally compiling. This process is the same for scripts in the editor and scripts in an exported game. Scripts are stored as text files in both cases, and the compilation process must happen in full before the bytecode can be passed to the virtual machine and run.
+
+The main class of the GDScript module is the [`GDScript`](gdscript.h) class, which represents a class defined in GDScript. Each `.gd` file is called a _class file_ because it implicitly defines a class in GDScript, and thus results in an associated `GDScript` object. However, GDScript classes may define [_inner classes_](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#inner-classes), and those are also represented by further `GDScript` objects, even though they are not in files of their own.
+
+The `GDScript` class contains all the information related to the corresponding GDScript class: its name and path, its members like variables, functions, symbols, signals, implicit methods like initializers, etc. This is the main class that the compilation step deals with.
+
+A secondary class is `GDScriptInstance`, defined in the same file, containing _runtime_ information for an instance of a `GDScript`, and is more related to the execution of a script by the virtual machine.
+
+
+### Loading source code
+
+This mostly happens by calling `GDScript::load_source_code()` on a `GDScript` object. Parsing only requires a `String`, so it is entirely possible to parse a script without a `GDScript` object!
+
+
+### Tokenizing (see [`GDScriptTokenizer`](gdscript_tokenizer.h))
+
+Tokenizing is the process of converting the source code `String` into a sequence of tokens, which represent language constructs (such as `for` or `if`), identifiers, literals, etc. This happens almost exclusively during the parsing process, which asks for the next token in order to make sense of the source code. The tokenizer is only used outside of the parsing process in very rare exceptions.
+
+
+### Parsing (see [`GDScriptParser`](gdscript_parser.h))
+
+The parser takes a sequence of tokens and builds [the abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the GDScript program. The AST is used in the analyzing and compilation steps, and the source code `String` and sequence of tokens are discarded. The AST-building process finds syntax errors in a GDScript program and reports them to the user.
+
+The parser class also defines all the possible nodes of the AST as subtypes of `GDScriptParser::Node`, not to be confused with Godot's scene tree `Node`. For example, `GDScriptParser::IfNode` has two children nodes, one for the code in the `if` block, and one for the code in the `else` block. A `GDScriptParser::FunctionNode` contains children nodes for its name, parameters, return type, body, etc. The parser also defines typechecking data structures like `GDScriptParser::Datatype`.
+
+The parser was [intentionally designed](https://godotengine.org/article/gdscript-progress-report-writing-new-parser/#less-lookahead) with a look-ahead of a single token. This means that the parser only has access to the current token and the previous token (or, if you prefer, the current token and the next token). This parsing limitation ensures that GDScript will remain syntactically simple and accessible, and that the parsing process cannot become overly complex.
+
+
+### Analysis and typechecking (see [`GDScriptAnalyzer`](gdscript_analyzer.h))
+
+The analyzer takes in the AST of a program and verifies that "everything checks out". For example, when analyzing a method call with three parameters, it will check whether the function definition also contains three parameters. If the code is typed, it will check that argument and parameter types are compatible.
+
+There are two types of functions in the analyzer: `reduce` functions and `resolve` functions. Their parameters always include the AST node that they are attempting to reduce or resolve.
+- The `reduce` functions work on GDScript expressions, which return values, and thus their main goal is to populate the `GDScriptParser::Datatype` of the underlying AST node. The datatype is then used to typecheck code that depends on this expression, and gives the compiler necessary information to generate appropriate, safe, and optimized bytecode.
+For example, function calls are handled with `reduce_call()`, which must figure out what function is being called and check that the passed arguments match the function's parameters. The type of the underlying `CallNode` will be the return type of the function.
+Another example is `reduce_identifier()`, which does _a lot_ of work: given the string of its `IdentifierNode`, it must figure out what that identifier refers to. It could be a local variable, class name, global or class function, function parameter, class or superclass member, or any number of other things. It has to check many different places to find this information!
+A secondary goal of the `reduce` functions is to perform [constant folding](https://en.wikipedia.org/wiki/Constant_folding): to determine whether an expression is constant, and if it is, compute its _reduced value_ at this time so it does not need to be computed over and over at runtime!
+- The resolve functions work on AST nodes that represent statements, and don't necessarily have values. Their goal is to do work related to program control flow, resolve their child AST nodes, deal with scoping, etc. One of the simplest examples is `resolve_if()`, which reduces the `if` condition, then resolves the `if` body and `else` body if it exists.
+The `resolve_for()` function does more work than simply resolving its code block. With `for i in range(10)`, for example, it must also declare and type the new variable `i` within the scope of its code block, as well as make sure `range(10)` is iterable, among other things.
+To understand classes and inheritance without introducing cyclic dependency problems that would come from immediate full class code analysis, the analyzer often asks only for class _interfaces_: it needs to know what member variables and methods exist as well as their types, but no more.
+This is done through `resolve_class_interface()`, which populates `ClassNode`'s `Datatype` with that information. It first checks for superclass information with `resolve_class_inheritance()`, then populates its member information by calling `resolve_class_member()` on each member. Since this step is only about the class _interface_, methods are resolved with `resolve_function_signature()`, which gets all relevant typing information without resolving the function body!
+The remaining steps of resolution, including member variable initialization code, method code, etc, can happen at a later time.
+
+In fully untyped code, very little static analysis is possible. For example, the analyzer cannot know whether `my_var.some_member` exists when it does not know the type of `my_var`. Therefore, it cannot emit a warning or error because `some_member` _could_ exist - or it could not. The analyzer must trust the programmer. If an error does occur, it will be at runtime.
+However, GDScript is gradually typed, so all of these analyses must work when parts of the code are typed and others untyped. Static analysis in a gradually typed language is a best-effort situation: suppose there is a typed variable `var x : int`, and an untyped `var y = "some string"`. We can obviously tell this isn't going to work, but the analyzer will accept the assignment `x = y` without warnings or errors: it only knows that `y` is untyped and can therefore be anything, including the `int` that `x` expects. It must once again trust the programmer to have written code that works. In this instance, the code will error at runtime.
+In both these cases, the analyzer handles the uncertainty of untyped code by calling `mark_node_unsafe()` on the respective AST node. This means it didn't have enough information to know whether the code was fully safe or necessarily wrong. Lines with unsafe AST nodes are represented by gray line numbers in the GDScript editor. Green line numbers indicate a line of code without any unsafe nodes.
+
+This analysis step is also where dependencies are introduced and that information stored for use later. If class `A` extends class `B` or contains a member with type `B` from some other script file, then the analyzer will attempt to load that second script. If `B` contains references to `A`, then a _cyclic_ dependency is introduced. This is OK in many cases, but impossible to resolve in others.
+
+Clearly, the analyzer is where a lot of the "magic" happens! It determines what constitutes proper code that can actually be compiled, and provides as many safety guarantees as possible with the typing information it is provided with. The more typed the code, the safer and more optimized it will be!
+
+
+#### Cyclic dependencies and member resolution
+
+Cyclic dependencies from inheritance (`A extends B, B extends A`) are not supported in any programming language. Other cyclic dependencies are supported, such as `A extends B` and `B` uses, contains, or preloads, members of type `A`.
+
+To see why cyclic dependencies are complicated, suppose there is one between classes `A <-> B`. Partially through the analysis of `A`, we will need information about `B`, and therefore trigger its analysis. However, the analysis of `B` will eventually need information from `A`, which is incomplete because we never finished analyzing it. This would result in members not being found when they actually exist!
+
+GDScript supports cyclic dependencies due to a few features of the analyzer:
+
+1. Class interface resolution: when analyzing code of class `A` that depends on some other class `B`, we don't need to resolve the _code_ of `B` (its member initializers, function code, etc). We only need to know what members and methods the class has, as well as their types. These are the only things one class can use to work with, or _interface_ with, another. Because of inheritance, a class's interface depends on its superclass as well, so recursive interface resolution is needed. More details can be found in `GDScriptAnalyzer::resolve_class_interface()`.
+2. Out of order member resolution: the analyzer may not even need an entire class interface to be resolved in order to figure out a specific type! For example, if class `A` contains code that references `B.is_alive`, then the analyzer doesn't need to immediately resolve `B`'s entire interface. It may simply check whether `is_alive` exists in `B`, and reduce it for its type information, on-demand.
+A fundamental cyclic dependency problem occurs when the types of two different member variables are mutually dependent. This is commonly checked by a pattern that declares a temporary datatype with `GDScriptParser::DataType resolving_datatype;`, followed by `resolving_datatype.kind = GDScriptParser::DataType::RESOLVING;`. If the analyzer attempts to resolve a member on-demand that is already tagged as resolving, then a cyclic dependency problem has been found and can be reported.
+
+
+### Compiling (see [`GDScriptCompiler`](gdscript_compiler.h))
+
+Compiling is the final step in making a GDScript executable in the [virtual machine](gdscript_vm.h) (VM). The compiler takes a `GDScript` object and an AST, and uses another class, [`GDScriptByteCodeGenerator`](gdscript_byte_codegen.h), to generate bytecode corresponding to the class. In doing this, it creates the objects that the VM understands how to run, like [`GDScriptFunction`](gdscript_function.h), and completes a few extra tasks needed for compilation, such as populating runtime class member information.
+
+Importantly, the compilation process of a class, specifically the `GDScriptCompiler::_compile_class()` method, _cannot_ depend on information obtained by calling `GDScriptCompiler::_compile_class()` on another class, for the same cyclic dependency reasons explained in the previous section.
+Any information that can only be obtained or populated during the compilation step, when `GDScript` objects become available, must be handled before `GDScriptCompiler::_compile_class()` is called. This process is centralized in `GDScriptCompiler::_prepare_compilation()` which works as the compile-time equivalent of `GDScriptAnalyzer::resolve_class_interface()`: it populates a `GDScript`'s "interface" exclusively with information from the analysis step, and without processing other external classes. This information may then be referenced by other classes without introducing problematic cycles.
+
+The more typing information a GDScript has, the more optimized the compiled bytecode can be. For example, if `my_var` is untyped, the bytecode for `my_var.some_member` will need to go through several layers of indirection to figure out the type of `my_var` at runtime, and from there determine how to obtain `some_member`. This varies depending on whether `my_var` is a dictionary, a script, or a native class. If the type of `my_var` was known at compile time, the bytecode can directly call the type-specific method for obtaining a member.
+Similar optimizations are possible for `my_var.some_func()`. With untyped GDScript, the VM will need to resolve `my_var`'s type at runtime, then, depending on the type, use different methods to resolve the function and call it. When the function is fully resolved during static analysis, native function pointers or GDScript function objects can be compiled into the bytecode and directly called by the VM, removing several layers of indirection.
+
+Typed code is safer code and faster code!
+
+
+## Loading scripts
+
+GDScripts can be loaded in a couple of different ways. The main method, used almost everywhere in the engine, is to load scripts through the `ResourceLoader` singleton. In this way, GDScripts are resources like any others: `ResourceLoader::load()` will simply reroute to `ResourceFormatLoaderGDScript::load()`, found in `gdscript.h/cpp`(gdscript.h). This generates a GDScript object which is compiled and ready to use.
+
+The other method is to manually load the source code, then pass it to a parser, then to an analyzer and then to a compiler. The previous approach does this behind the scenes, alongside some smart caching of scripts and other functionalities. It is used in the [GDScript test runner infrastructure](tests/gdscript_test_runner.h).
+
+
+### Full and shallow scripts
+
+The `ResourceFormatLoaderGDScript::load()` method simply calls `GDScriptCache::get_full_script()`. The [`GDScriptCache`](gdscript_cache.h) is, as it sounds, a cache for GDScripts. Its two main methods, `get_shallow_script()` and `get_full_script()`, get and cache, respectively, scripts that have been merely parsed, and scripts which have been statically analyzed and fully compiled. Another internal class, `GDScriptParserRef`, found in the same file, provides even more granularity over the different steps of the parsing process, and is used extensively in the analyzer.
+
+Shallow, or "just parsed" scripts, provide information such as defined classes, class members, and so forth. This is sufficient for many purposes, like obtaining a class interface or checking whether a member exists on a specific class. Full scripts, on the other hand, have been analyzed and compiled and are ready to use.
+
+The distinction between full and shallow scripts is very important, as shallow scripts cannot create cyclic dependency problems, whereas full scripts can. The analyzer, for example, never asks for full scripts. Choosing when to request a shallow vs a full script is an important but subtle decision.
+
+In practice, full scripts are simply scripts where `GDScript::reload()` has been called. This critical function is the primary way in which scripts get compiled in Godot, and essentially does all the compilation steps covered so far in order. Whenever a script is loaded, or updated and reloaded in Godot, it will end up going through `GDScript::reload()`, except in very rare circumstances like the test runner. It is an excellent place to start reading and understanding the GDScript module!
+
+
+## Special types of scripts
+
+Certain types of GDScripts behave slightly differently. For example, autoloads are loaded with `ResourceLoader::load()` during `Main::start()`, very soon after Godot is launched. Many systems aren't initialized at that time, so error reporting is often significantly reduced and may not even show up in the editor.
+
+Tool scripts, declared with the `@tool` annotation on a GDScript file, run in the editor itself as opposed to just when the game is launched. This leads to a significant increase in complexity, as many things that can be changed in the editor may affect a currently executing tool script.
+
+
+## Other
+
+There are many other classes in the GDScript module. Here is a brief overview of some of them:
+
+- Declaration of GDScript warnings in [`GDScriptWarning`](gdscript_warning.h).
+- [`GDScriptFunction`](gdscript_function.h), which represents an executable GDScript function. The relevant file contains both static as well as runtime information.
+- The [virtual machine](gdscript_vm.cpp) is essentially defined as calling `GDScriptFunction::call()`.
+- Editor-related functions can be found in parts of `GDScriptLanguage`, originally declared in [`gdscript.h`](gdscript.h) but defined in [`gdscript_editor.cpp`](gdscript_editor.cpp). Code highlighting can be found in [`GDScriptSyntaxHighlighter`](editor/gdscript_highlighter.h).
+- GDScript decompilation is found in [`gdscript_disassembler.cpp`](gdscript_disassembler.h), defined as `GDScriptFunction::disassemble()`.
+- Documentation generation from GDScript comments in [`GDScriptDocGen`](editor/gdscript_docgen.h) \ No newline at end of file
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index fcb7a11a14..933bfba5ba 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -148,7 +148,7 @@
<return type="int" />
<param index="0" name="var" type="Variant" />
<description>
- Returns the length of the given Variant [param var]. The length can be the character count of a [String], the element count of any array type or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
+ Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
[codeblock]
a = [1, 2, 3, 4]
len(a) # Returns 4
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 00179109a3..659140b9b1 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -32,21 +32,29 @@
#include "../gdscript.h"
-String GDScriptDocGen::_get_script_path(const String &p_path) {
+#include "core/config/project_settings.h"
+
+HashMap<String, String> GDScriptDocGen::singletons;
+
+String GDScriptDocGen::_get_script_name(const String &p_path) {
+ const HashMap<String, String>::ConstIterator E = singletons.find(p_path);
+ if (E) {
+ return E->value;
+ }
return p_path.trim_prefix("res://").quote();
}
String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) {
const GDP::ClassNode *curr_class = &p_class;
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
- return _get_script_path(curr_class->fqcn);
+ return _get_script_name(curr_class->fqcn);
}
String full_name = curr_class->identifier->name;
while (curr_class->outer) {
curr_class = curr_class->outer;
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
- return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name);
+ return vformat("%s.%s", _get_script_name(curr_class->fqcn), full_name);
}
full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
}
@@ -97,12 +105,12 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
if (!p_gdtype.script_type->get_path().is_empty()) {
- r_type = _get_script_path(p_gdtype.script_type->get_path());
+ r_type = _get_script_name(p_gdtype.script_type->get_path());
return;
}
}
if (!p_gdtype.script_path.is_empty()) {
- r_type = _get_script_path(p_gdtype.script_path);
+ r_type = _get_script_name(p_gdtype.script_path);
return;
}
r_type = "Object";
@@ -221,24 +229,25 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
}
}
-void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
+void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
p_script->_clear_doc();
DocData::ClassDoc &doc = p_script->doc;
- doc.script_path = _get_script_path(p_script->get_script_path());
+ doc.is_script_doc = true;
+
if (p_script->local_name == StringName()) {
- doc.name = doc.script_path;
+ // This is an outer unnamed class.
+ doc.name = _get_script_name(p_script->get_script_path());
} else {
+ // This is an inner or global outer class.
doc.name = p_script->local_name;
+ if (p_script->_owner) {
+ doc.name = p_script->_owner->doc.name + "." + doc.name;
+ }
}
- if (p_script->_owner) {
- doc.name = p_script->_owner->doc.name + "." + doc.name;
- doc.script_path = doc.script_path + "." + doc.name;
- }
-
- doc.is_script_doc = true;
+ doc.script_path = p_script->get_script_path();
if (p_script->base.is_valid() && p_script->base->is_valid()) {
if (!p_script->base->doc.name.is_empty()) {
@@ -271,7 +280,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
// Recursively generate inner class docs.
// Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts().
- GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class);
+ GDScriptDocGen::_generate_docs(*p_script->subclasses[class_name], inner_class);
} break;
case GDP::ClassNode::Member::CONSTANT: {
@@ -451,3 +460,13 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
// Add doc to the outer-most class.
p_script->_add_doc(doc);
}
+
+void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
+ for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
+ if (E.value.is_singleton) {
+ singletons[E.value.path] = E.key;
+ }
+ }
+ _generate_docs(p_script, p_class);
+ singletons.clear();
+}
diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h
index a326c02c5f..651a4fb198 100644
--- a/modules/gdscript/editor/gdscript_docgen.h
+++ b/modules/gdscript/editor/gdscript_docgen.h
@@ -39,10 +39,13 @@ class GDScriptDocGen {
using GDP = GDScriptParser;
using GDType = GDP::DataType;
- static String _get_script_path(const String &p_path);
+ static HashMap<String, String> singletons; // Script path to singleton name.
+
+ static String _get_script_name(const String &p_path);
static String _get_class_name(const GDP::ClassNode &p_class);
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
+ static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
public:
static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 3df07f9794..426565bb68 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "editor/editor_settings.h"
+#include "editor/themes/editor_theme_manager.h"
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
Dictionary color_map;
@@ -62,13 +63,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_member_variable = false;
bool in_lambda = false;
- bool in_function_name = false;
- bool in_variable_declaration = false;
+ bool in_function_name = false; // Any call.
+ bool in_function_declaration = false; // Only declaration.
+ bool in_var_const_declaration = false;
bool in_signal_declaration = false;
bool expect_type = false;
- int in_function_args = 0;
- int in_function_arg_dicts = 0;
+ int in_declaration_params = 0; // The number of opened `(` after func/signal name.
+ int in_declaration_param_dicts = 0; // The number of opened `{` inside func params.
+ int in_type_params = 0; // The number of opened `[` after type name.
Color keyword_color;
Color color;
@@ -149,7 +152,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
// Don't skip comments, for highlighting markers.
- if (color_regions[in_region].start_key.begins_with("#")) {
+ if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) {
break;
}
if (from + end_key_length > line_length) {
@@ -171,7 +174,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
// Don't skip comments, for highlighting markers.
- if (j == line_length && !color_regions[in_region].start_key.begins_with("#")) {
+ if (j == line_length && color_regions[in_region].type != ColorRegion::TYPE_COMMENT) {
continue;
}
}
@@ -179,13 +182,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// If we are in one, find the end key.
if (in_region != -1) {
Color region_color = color_regions[in_region].color;
- if (in_node_path && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
+ if (in_node_path && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = node_path_color;
}
- if (in_node_ref && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
+ if (in_node_ref && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = node_ref_color;
}
- if (in_string_name && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
+ if (in_string_name && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = string_name_color;
}
@@ -193,7 +196,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
- if (color_regions[in_region].start_key.begins_with("#")) {
+ if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) {
int marker_start_pos = from;
int marker_len = 0;
while (from <= line_length) {
@@ -444,12 +447,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (str[k] == '(') {
in_function_name = true;
- } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) {
- in_variable_declaration = true;
+ if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
+ in_function_declaration = true;
+ }
+ } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::CONST)) {
+ in_var_const_declaration = true;
}
// Check for lambda.
- if (in_function_name && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
+ if (in_function_declaration) {
k = j - 1;
while (k > 0 && is_whitespace(str[k])) {
k--;
@@ -474,48 +480,60 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (is_a_symbol) {
- if (in_function_args > 0) {
+ if (in_declaration_params > 0) {
switch (str[j]) {
case '(':
- in_function_args += 1;
+ in_declaration_params += 1;
break;
case ')':
- in_function_args -= 1;
+ in_declaration_params -= 1;
break;
case '{':
- in_function_arg_dicts += 1;
+ in_declaration_param_dicts += 1;
break;
case '}':
- in_function_arg_dicts -= 1;
+ in_declaration_param_dicts -= 1;
break;
}
- } else if (in_function_name && str[j] == '(') {
- in_function_args = 1;
- in_function_arg_dicts = 0;
- }
-
- if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != ',' && str[j] != '.') {
- expect_type = false;
+ } else if ((in_function_declaration || in_signal_declaration || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) && str[j] == '(') {
+ in_declaration_params = 1;
+ in_declaration_param_dicts = 0;
}
- if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
- expect_type = true;
- }
-
- if (in_variable_declaration || in_function_args > 0) {
- int k = j;
- // Skip space.
- while (k < line_length && is_whitespace(str[k])) {
- k++;
+ if (expect_type) {
+ switch (str[j]) {
+ case '[':
+ in_type_params += 1;
+ break;
+ case ']':
+ in_type_params -= 1;
+ break;
+ case ',':
+ if (in_type_params <= 0) {
+ expect_type = false;
+ }
+ break;
+ case ' ':
+ case '\t':
+ case '.':
+ break;
+ default:
+ expect_type = false;
+ break;
}
-
- if (str[k] == ':' && in_function_arg_dicts == 0) {
- // Has type hint.
+ } else {
+ if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
expect_type = true;
+ in_type_params = 0;
+ }
+ if ((in_var_const_declaration || (in_declaration_params == 1 && in_declaration_param_dicts == 0)) && str[j] == ':') {
+ expect_type = true;
+ in_type_params = 0;
}
}
- in_variable_declaration = false;
+ in_function_declaration = false;
+ in_var_const_declaration = false;
in_signal_declaration = false;
in_function_name = false;
in_lambda = false;
@@ -581,7 +599,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
color = member_color;
} else if (in_function_name) {
next_type = FUNCTION;
- if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
+ if (!in_lambda && in_function_declaration) {
color = function_definition_color;
} else {
color = function_color;
@@ -737,7 +755,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
for (const String &comment : comments) {
String beg = comment.get_slice(" ", 0);
String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
- add_color_region(beg, end, comment_color, end.is_empty());
+ add_color_region(ColorRegion::TYPE_COMMENT, beg, end, comment_color, end.is_empty());
}
/* Doc comments */
@@ -747,18 +765,20 @@ void GDScriptSyntaxHighlighter::_update_cache() {
for (const String &doc_comment : doc_comments) {
String beg = doc_comment.get_slice(" ", 0);
String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String();
- add_color_region(beg, end, doc_comment_color, end.is_empty());
+ add_color_region(ColorRegion::TYPE_COMMENT, beg, end, doc_comment_color, end.is_empty());
}
+ /* Code regions */
+ const Color code_region_color = Color(EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color").operator Color(), 1.0);
+ add_color_region(ColorRegion::TYPE_CODE_REGION, "#region", "", code_region_color, true);
+ add_color_region(ColorRegion::TYPE_CODE_REGION, "#endregion", "", code_region_color, true);
+
/* Strings */
string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
- List<String> strings;
- gdscript->get_string_delimiters(&strings);
- for (const String &string : strings) {
- String beg = string.get_slice(" ", 0);
- String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String();
- add_color_region(beg, end, string_color, end.is_empty());
- }
+ add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color);
+ add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color);
+ add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color);
+ add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color);
const Ref<Script> scr = _get_edited_resource();
if (scr.is_valid()) {
@@ -790,7 +810,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme");
const bool godot_2_theme = text_edit_color_theme == "Godot 2";
- if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) {
+ if (godot_2_theme || EditorThemeManager::is_dark_theme()) {
function_definition_color = Color(0.4, 0.9, 1.0);
global_function_color = Color(0.64, 0.64, 0.96);
node_path_color = Color(0.72, 0.77, 0.49);
@@ -891,20 +911,17 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
-void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
- for (int i = 0; i < p_start_key.length(); i++) {
- ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "color regions must start with a symbol");
- }
+void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
+ ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty.");
+ ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol.");
- if (p_end_key.length() > 0) {
- for (int i = 0; i < p_end_key.length(); i++) {
- ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "color regions must end with a symbol");
- }
+ if (!p_end_key.is_empty()) {
+ ERR_FAIL_COND_MSG(!is_symbol(p_end_key[0]), "Color region end key must start with a symbol.");
}
int at = 0;
for (int i = 0; i < color_regions.size(); i++) {
- ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists.");
+ ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "Color region with start key '" + p_start_key + "' already exists.");
if (p_start_key.length() < color_regions[i].start_key.length()) {
at++;
} else {
@@ -913,6 +930,7 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons
}
ColorRegion color_region;
+ color_region.type = p_type;
color_region.color = p_color;
color_region.start_key = p_start_key;
color_region.end_key = p_end_key;
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index 090857f397..eb7bb7d801 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -39,6 +39,15 @@ class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter {
private:
struct ColorRegion {
+ enum Type {
+ TYPE_NONE,
+ TYPE_STRING, // `"` and `'`, optional prefix `&`, `^`, or `r`.
+ TYPE_MULTILINE_STRING, // `"""` and `'''`, optional prefix `r`.
+ TYPE_COMMENT, // `#` and `##`.
+ TYPE_CODE_REGION, // `#region` and `#endregion`.
+ };
+
+ Type type = TYPE_NONE;
Color color;
String start_key;
String end_key;
@@ -94,7 +103,7 @@ private:
Color comment_marker_colors[COMMENT_MARKER_MAX];
HashMap<String, CommentMarkerLevel> comment_markers;
- void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false);
+ void add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false);
public:
virtual void _update_cache() override;
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index f55b00ebe1..316281209a 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -47,10 +47,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
Error err;
Ref<Resource> loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
- if (err) {
- ERR_PRINT("Failed to load " + p_path);
- return err;
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to load " + p_path);
ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;
@@ -59,11 +56,11 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
GDScriptParser parser;
err = parser.parse(source_code, p_path, false);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to parse GDScript with GDScriptParser.");
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to parse GDScript with GDScriptParser.");
GDScriptAnalyzer analyzer(&parser);
err = analyzer.analyze();
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
@@ -197,11 +194,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar
_assess_expression(binary_op_node->right_operand);
} break;
case GDScriptParser::Node::CALL: {
- const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression);
- _extract_from_call(call_node);
- for (int i = 0; i < call_node->arguments.size(); i++) {
- _assess_expression(call_node->arguments[i]);
- }
+ _assess_call(static_cast<const GDScriptParser::CallNode *>(p_expression));
} break;
case GDScriptParser::Node::CAST: {
_assess_expression(static_cast<const GDScriptParser::CastNode *>(p_expression)->operand);
@@ -241,6 +234,9 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar
}
void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptParser::AssignmentNode *p_assignment) {
+ _assess_expression(p_assignment->assignee);
+ _assess_expression(p_assignment->assigned_value);
+
// Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____"
StringName assignee_name;
@@ -258,26 +254,18 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
ids->push_back(p_assignment->assigned_value->reduced_value);
- } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
- // FileDialog.filters accepts assignment in the form of PackedStringArray. For example,
- // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
-
- const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_assignment->assigned_value);
- if (!call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
- const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
-
- // Extract the name in "extension ; name" of PackedStringArray.
- for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_constant_strings(array_node->elements[i]);
- }
- }
- } else {
- // If the assignee is not in extract patterns or the assigned_value is not a constant string, try to see if the assigned_value contains tr().
- _assess_expression(p_assignment->assigned_value);
+ } else if (assignee_name == fd_filters) {
+ // Extract from `get_node("FileDialog").filters = <filter array>`.
+ _extract_fd_filter_array(p_assignment->assigned_value);
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_from_call(const GDScriptParser::CallNode *p_call) {
+void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::CallNode *p_call) {
+ _assess_expression(p_call->callee);
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ _assess_expression(p_call->arguments[i]);
+ }
+
// Extract the translatable strings coming from function calls. For example:
// tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____").
@@ -322,52 +310,56 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(const GDScriptPar
ids_ctx_plural->push_back(id_ctx_plural);
}
} else if (first_arg_patterns.has(function_name)) {
- if (_is_constant_string(p_call->arguments[0])) {
+ if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
ids->push_back(p_call->arguments[0]->reduced_value);
}
} else if (second_arg_patterns.has(function_name)) {
- if (_is_constant_string(p_call->arguments[1])) {
+ if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
ids->push_back(p_call->arguments[1]->reduced_value);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
- _extract_fd_constant_strings(p_call->arguments[0]);
- } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) {
- // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example,
- // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])).
-
- const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_call->arguments[0]);
- if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
- const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
- for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_constant_strings(array_node->elements[i]);
- }
+ if (!p_call->arguments.is_empty()) {
+ _extract_fd_filter_string(p_call->arguments[0]);
}
- }
-
- if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
- const GDScriptParser::SubscriptNode *subscript_node = static_cast<const GDScriptParser::SubscriptNode *>(p_call->callee);
- if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) {
- const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(subscript_node->base);
- _extract_from_call(call_node);
+ } else if (function_name == fd_set_filter) {
+ // Extract from `get_node("FileDialog").set_filters(<filter array>)`.
+ if (!p_call->arguments.is_empty()) {
+ _extract_fd_filter_array(p_call->arguments[0]);
}
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression) {
+void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
// Extract the name in "extension ; name".
-
if (_is_constant_string(p_expression)) {
- String arg_val = p_expression->reduced_value;
- PackedStringArray arr = arg_val.split(";", true);
- if (arr.size() != 2) {
- ERR_PRINT("Argument for setting FileDialog has bad format.");
- return;
- }
+ PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
+ ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
ids->push_back(arr[1].strip_edges());
}
}
+void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression) {
+ const GDScriptParser::ArrayNode *array_node = nullptr;
+
+ if (p_expression->type == GDScriptParser::Node::ARRAY) {
+ // Extract from `["*.png ; PNG Images","*.gd ; GDScript Files"]` (implicit cast to `PackedStringArray`).
+ array_node = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
+ } else if (p_expression->type == GDScriptParser::Node::CALL) {
+ // Extract from `PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])`.
+ const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression);
+ if (call_node->get_callee_type() == GDScriptParser::Node::IDENTIFIER && call_node->function_name == SNAME("PackedStringArray") && !call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ }
+ }
+
+ if (array_node) {
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_filter_string(array_node->elements[i]);
+ }
+ }
+}
+
GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
assignment_patterns.insert("text");
assignment_patterns.insert("placeholder_text");
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index fab79a925f..fe876134c2 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -61,8 +61,10 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
void _assess_expression(const GDScriptParser::ExpressionNode *p_expression);
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
- void _extract_from_call(const GDScriptParser::CallNode *p_call);
- void _extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression);
+ void _assess_call(const GDScriptParser::CallNode *p_call);
+
+ void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
+ void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
index 28ab080dd2..bd4816827f 100644
--- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
+++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
@@ -6,14 +6,11 @@ extends _BASE_
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
-# Get the gravity from the project settings to be synced with RigidBody nodes.
-var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity")
-
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
- velocity.y += gravity * delta
+ velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
index 9b0e4be4ed..f9c4f70a24 100644
--- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
+++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
@@ -6,14 +6,11 @@ extends _BASE_
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
-# Get the gravity from the project settings to be synced with RigidBody nodes.
-var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
-
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
- velocity.y -= gravity * delta
+ velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index a999acd1bd..d7aaf4ce10 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -35,6 +35,7 @@
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "gdscript_rpc_callable.h"
+#include "gdscript_tokenizer_buffer.h"
#include "gdscript_warning.h"
#ifdef TOOLS_ENABLED
@@ -162,13 +163,14 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
_super_implicit_constructor(this, instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
+ String error_text = Variant::get_call_error_text(instance->owner, "@implicit_new", nullptr, 0, r_error);
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
- ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+ ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
if (p_argcount < 0) {
@@ -179,13 +181,14 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
if (initializer != nullptr) {
initializer->call(instance, p_args, p_argcount, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
+ String error_text = Variant::get_call_error_text(instance->owner, "_init", p_args, p_argcount, r_error);
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
- ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+ ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
}
//@TODO make thread safe
@@ -738,7 +741,12 @@ Error GDScript::reload(bool p_keep_state) {
valid = false;
GDScriptParser parser;
- Error err = parser.parse(source, path, false);
+ Error err;
+ if (!binary_tokens.is_empty()) {
+ err = parser.parse_binary(binary_tokens, path);
+ } else {
+ err = parser.parse(source, path, false);
+ }
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
@@ -1048,6 +1056,19 @@ Error GDScript::load_source_code(const String &p_path) {
return OK;
}
+void GDScript::set_binary_tokens_source(const Vector<uint8_t> &p_binary_tokens) {
+ binary_tokens = p_binary_tokens;
+}
+
+const Vector<uint8_t> &GDScript::get_binary_tokens_source() const {
+ return binary_tokens;
+}
+
+Vector<uint8_t> GDScript::get_as_binary_tokens() const {
+ GDScriptTokenizerBuffer tokenizer;
+ return tokenizer.parse_code_string(source, GDScriptTokenizerBuffer::COMPRESS_NONE);
+}
+
const HashMap<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions() const {
return member_functions;
}
@@ -1149,7 +1170,7 @@ GDScript *GDScript::get_root_script() {
RBSet<GDScript *> GDScript::get_dependencies() {
RBSet<GDScript *> dependencies;
- _get_dependencies(dependencies, this);
+ _collect_dependencies(dependencies, this);
dependencies.erase(this);
return dependencies;
@@ -1255,52 +1276,55 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
return Object::cast_to<GDScript>(obj);
}
-void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+void GDScript::_collect_function_dependencies(GDScriptFunction *p_func, RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+ if (p_func == nullptr) {
+ return;
+ }
+ for (GDScriptFunction *lambda : p_func->lambdas) {
+ _collect_function_dependencies(lambda, p_dependencies, p_except);
+ }
+ for (const Variant &V : p_func->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_collect_dependencies(p_dependencies, p_except);
+ }
+ }
+}
+
+void GDScript::_collect_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
if (p_dependencies.has(this)) {
return;
}
- p_dependencies.insert(this);
+ if (this != p_except) {
+ p_dependencies.insert(this);
+ }
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- if (E.value == nullptr) {
- continue;
- }
- for (const Variant &V : E.value->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(E.value, p_dependencies, p_except);
}
if (implicit_initializer) {
- for (const Variant &V : implicit_initializer->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(implicit_initializer, p_dependencies, p_except);
}
if (implicit_ready) {
- for (const Variant &V : implicit_ready->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(implicit_ready, p_dependencies, p_except);
+ }
+
+ if (static_initializer) {
+ _collect_function_dependencies(static_initializer, p_dependencies, p_except);
}
for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
if (E.value != p_except) {
- E.value->_get_dependencies(p_dependencies, p_except);
+ E.value->_collect_dependencies(p_dependencies, p_except);
}
}
for (const KeyValue<StringName, Variant> &E : constants) {
GDScript *scr = _get_gdscript_from_variant(E.value);
if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
+ scr->_collect_dependencies(p_dependencies, p_except);
}
}
}
@@ -1381,51 +1405,43 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) {
}
#endif
-GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_main_thread;
-thread_local GDScript::UpdatableFuncPtr *GDScript::func_ptrs_to_update_thread_local = nullptr;
-
-GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
- UpdatableFuncPtrElement result = {};
-
- {
- MutexLock lock(func_ptrs_to_update_thread_local->mutex);
- result.element = func_ptrs_to_update_thread_local->ptrs.push_back(p_func_ptr_ptr);
- result.func_ptr = func_ptrs_to_update_thread_local;
-
- if (likely(func_ptrs_to_update_thread_local->initialized)) {
- return result;
- }
-
- func_ptrs_to_update_thread_local->initialized = true;
+GDScript::UpdatableFuncPtr::UpdatableFuncPtr(GDScriptFunction *p_function) {
+ if (p_function == nullptr) {
+ return;
}
- MutexLock lock(func_ptrs_to_update_mutex);
- func_ptrs_to_update.push_back(func_ptrs_to_update_thread_local);
- func_ptrs_to_update_thread_local->rc++;
-
- return result;
-}
+ ptr = p_function;
+ script = ptr->get_script();
+ ERR_FAIL_NULL(script);
-void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element) {
- ERR_FAIL_NULL(p_func_ptr_element.element);
- ERR_FAIL_NULL(p_func_ptr_element.func_ptr);
- MutexLock lock(p_func_ptr_element.func_ptr->mutex);
- p_func_ptr_element.element->erase();
+ MutexLock script_lock(script->func_ptrs_to_update_mutex);
+ list_element = script->func_ptrs_to_update.push_back(this);
}
-void GDScript::_fixup_thread_function_bookkeeping() {
- // Transfer the ownership of these update items to the main thread,
- // because the current one is dying, leaving theirs orphan, dangling.
+GDScript::UpdatableFuncPtr::~UpdatableFuncPtr() {
+ ERR_FAIL_NULL(script);
- DEV_ASSERT(!Thread::is_main_thread());
+ if (list_element) {
+ MutexLock script_lock(script->func_ptrs_to_update_mutex);
+ list_element->erase();
+ list_element = nullptr;
+ }
+}
- MutexLock lock(func_ptrs_to_update_main_thread.mutex);
- MutexLock lock2(func_ptrs_to_update_thread_local->mutex);
+void GDScript::_recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const {
+ MutexLock lock(func_ptrs_to_update_mutex);
+ for (UpdatableFuncPtr *updatable : func_ptrs_to_update) {
+ HashMap<GDScriptFunction *, GDScriptFunction *>::ConstIterator replacement = p_replacements.find(updatable->ptr);
+ if (replacement) {
+ updatable->ptr = replacement->value;
+ } else {
+ // Probably a lambda from another reload, ignore.
+ updatable->ptr = nullptr;
+ }
+ }
- while (!func_ptrs_to_update_thread_local->ptrs.is_empty()) {
- List<GDScriptFunction **>::Element *E = func_ptrs_to_update_thread_local->ptrs.front();
- E->transfer_to_back(&func_ptrs_to_update_main_thread.ptrs);
- func_ptrs_to_update_thread_local->transferred = true;
+ for (HashMap<StringName, Ref<GDScript>>::ConstIterator subscript = subclasses.begin(); subscript; ++subscript) {
+ subscript->value->_recurse_replace_function_ptrs(p_replacements);
}
}
@@ -1447,30 +1463,9 @@ void GDScript::clear(ClearData *p_clear_data) {
}
{
- MutexLock outer_lock(func_ptrs_to_update_mutex);
+ MutexLock lock(func_ptrs_to_update_mutex);
for (UpdatableFuncPtr *updatable : func_ptrs_to_update) {
- bool destroy = false;
- {
- MutexLock inner_lock(updatable->mutex);
- if (updatable->transferred) {
- func_ptrs_to_update_main_thread.mutex.lock();
- }
- for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) {
- *func_ptr_ptr = nullptr;
- }
- DEV_ASSERT(updatable->rc != 0);
- updatable->rc--;
- if (updatable->rc == 0) {
- destroy = true;
- }
- if (updatable->transferred) {
- func_ptrs_to_update_main_thread.mutex.unlock();
- }
- }
- if (destroy) {
- DEV_ASSERT(updatable != &func_ptrs_to_update_main_thread);
- memdelete(updatable);
- }
+ updatable->ptr = nullptr;
}
}
@@ -1543,6 +1538,13 @@ GDScript::~GDScript() {
}
destructing = true;
+ if (is_print_verbose_enabled()) {
+ MutexLock lock(func_ptrs_to_update_mutex);
+ if (!func_ptrs_to_update.is_empty()) {
+ print_line(vformat("GDScript: %d orphaned lambdas becoming invalid at destruction of script '%s'.", func_ptrs_to_update.size(), fully_qualified_name));
+ }
+ }
+
clear();
{
@@ -1687,7 +1689,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
{
HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name);
if (E) {
- r_ret = Signal(this->owner, E->key);
+ r_ret = Signal(owner, E->key);
return true;
}
}
@@ -1696,9 +1698,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name);
if (E) {
if (sptr->rpc_config.has(p_name)) {
- r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
+ r_ret = Callable(memnew(GDScriptRPCCallable(owner, E->key)));
} else {
- r_ret = Callable(this->owner, E->key);
+ r_ret = Callable(owner, E->key);
}
return true;
}
@@ -2091,33 +2093,6 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
named_globals.erase(p_name);
}
-void GDScriptLanguage::thread_enter() {
- GDScript::func_ptrs_to_update_thread_local = memnew(GDScript::UpdatableFuncPtr);
-}
-
-void GDScriptLanguage::thread_exit() {
- // This thread may have been created before GDScript was up
- // (which also means it can't have run any GDScript code at all).
- if (!GDScript::func_ptrs_to_update_thread_local) {
- return;
- }
-
- GDScript::_fixup_thread_function_bookkeeping();
-
- bool destroy = false;
- {
- MutexLock lock(GDScript::func_ptrs_to_update_thread_local->mutex);
- DEV_ASSERT(GDScript::func_ptrs_to_update_thread_local->rc != 0);
- GDScript::func_ptrs_to_update_thread_local->rc--;
- if (GDScript::func_ptrs_to_update_thread_local->rc == 0) {
- destroy = true;
- }
- }
- if (destroy) {
- memdelete(GDScript::func_ptrs_to_update_thread_local);
- }
-}
-
void GDScriptLanguage::init() {
//populate global constants
int gcc = CoreConstants::get_global_constant_count();
@@ -2150,8 +2125,6 @@ void GDScriptLanguage::init() {
_add_global(E.name, E.ptr);
}
- GDScript::func_ptrs_to_update_thread_local = &GDScript::func_ptrs_to_update_main_thread;
-
#ifdef TESTS_ENABLED
GDScriptTests::GDScriptTestRunner::handle_cmdline();
#endif
@@ -2201,13 +2174,11 @@ void GDScriptLanguage::finish() {
}
script_list.clear();
function_list.clear();
-
- DEV_ASSERT(GDScript::func_ptrs_to_update_main_thread.rc == 1);
}
void GDScriptLanguage::profiling_start() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -2238,7 +2209,7 @@ void GDScriptLanguage::profiling_set_save_native_calls(bool p_enable) {
void GDScriptLanguage::profiling_stop() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
profiling = false;
#endif
@@ -2248,7 +2219,7 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
int current = 0;
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
profiling_collate_native_call_data(true);
SelfList<GDScriptFunction> *elem = function_list.first();
@@ -2286,7 +2257,7 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_
int current = 0;
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
profiling_collate_native_call_data(false);
SelfList<GDScriptFunction> *elem = function_list.first();
@@ -2373,14 +2344,13 @@ struct GDScriptDepSort {
void GDScriptLanguage::reload_all_scripts() {
#ifdef DEBUG_ENABLED
print_verbose("GDScript: Reloading all scripts");
- List<Ref<GDScript>> scripts;
+ Array scripts;
{
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
- // Scripts will reload all subclasses, so only reload root scripts.
- if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
+ if (elem->self()->get_path().is_resource_file()) {
print_verbose("GDScript: Found: " + elem->self()->get_path());
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
@@ -2401,24 +2371,16 @@ void GDScriptLanguage::reload_all_scripts() {
#endif
}
- //as scripts are going to be reloaded, must proceed without locking here
-
- scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
-
- for (Ref<GDScript> &scr : scripts) {
- print_verbose("GDScript: Reloading: " + scr->get_path());
- scr->load_source_code(scr->get_path());
- scr->reload(true);
- }
+ reload_scripts(scripts, true);
#endif
}
-void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
#ifdef DEBUG_ENABLED
List<Ref<GDScript>> scripts;
{
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
@@ -2439,7 +2401,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
for (Ref<GDScript> &scr : scripts) {
- bool reload = scr == p_script || to_reload.has(scr->get_base());
+ bool reload = p_scripts.has(scr) || to_reload.has(scr->get_base());
if (!reload) {
continue;
@@ -2462,7 +2424,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
}
}
-//same thing for placeholders
+ //same thing for placeholders
#ifdef TOOLS_ENABLED
while (scr->placeholders.size()) {
@@ -2490,6 +2452,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
Ref<GDScript> scr = E.key;
+ print_verbose("GDScript: Reloading: " + scr->get_path());
+ scr->load_source_code(scr->get_path());
scr->reload(p_soft_reload);
//restore state if saved
@@ -2537,12 +2501,18 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
#endif
}
+void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+ Array scripts;
+ scripts.push_back(p_script);
+ reload_scripts(scripts, p_soft_reload);
+}
+
void GDScriptLanguage::frame() {
calls = 0;
#ifdef DEBUG_ENABLED
if (profiling) {
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -2857,6 +2827,7 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
+ p_extensions->push_back("gdc");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
@@ -2865,7 +2836,7 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
- if (el == "gd") {
+ if (el == "gd" || el == "gdc") {
return "GDScript";
}
return "";
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 31811bba47..4ec97fa6b8 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -117,29 +117,28 @@ class GDScript : public Script {
HashMap<GDScriptFunction *, LambdaInfo> lambda_info;
- // List is used here because a ptr to elements are stored, so the memory locations need to be stable
- struct UpdatableFuncPtr {
- List<GDScriptFunction **> ptrs;
- Mutex mutex;
- bool initialized : 1;
- bool transferred : 1;
- uint32_t rc = 1;
- UpdatableFuncPtr() :
- initialized(false), transferred(false) {}
- };
- struct UpdatableFuncPtrElement {
- List<GDScriptFunction **>::Element *element = nullptr;
- UpdatableFuncPtr *func_ptr = nullptr;
+public:
+ class UpdatableFuncPtr {
+ friend class GDScript;
+
+ GDScriptFunction *ptr = nullptr;
+ GDScript *script = nullptr;
+ List<UpdatableFuncPtr *>::Element *list_element = nullptr;
+
+ public:
+ GDScriptFunction *operator->() const { return ptr; }
+ operator GDScriptFunction *() const { return ptr; }
+
+ UpdatableFuncPtr(GDScriptFunction *p_function);
+ ~UpdatableFuncPtr();
};
- static UpdatableFuncPtr func_ptrs_to_update_main_thread;
- static thread_local UpdatableFuncPtr *func_ptrs_to_update_thread_local;
+
+private:
+ // List is used here because a ptr to elements are stored, so the memory locations need to be stable
List<UpdatableFuncPtr *> func_ptrs_to_update;
Mutex func_ptrs_to_update_mutex;
- UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr);
- static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element);
-
- static void _fixup_thread_function_bookkeeping();
+ void _recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const;
#ifdef TOOLS_ENABLED
// For static data storage during hot-reloading.
@@ -177,6 +176,7 @@ class GDScript : public Script {
bool clearing = false;
//exported members
String source;
+ Vector<uint8_t> binary_tokens;
String path;
bool path_valid = false; // False if using default path.
StringName local_name; // Inner class identifier or `class_name`.
@@ -213,7 +213,8 @@ class GDScript : public Script {
void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const;
GDScript *_get_gdscript_from_variant(const Variant &p_variant);
- void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
+ void _collect_function_dependencies(GDScriptFunction *p_func, RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
+ void _collect_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -297,6 +298,10 @@ public:
String get_script_path() const;
Error load_source_code(const String &p_path);
+ void set_binary_tokens_source(const Vector<uint8_t> &p_binary_tokens);
+ const Vector<uint8_t> &get_binary_tokens_source() const;
+ Vector<uint8_t> get_as_binary_tokens() const;
+
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
virtual void get_script_method_list(List<MethodInfo> *p_list) const override;
@@ -541,7 +546,7 @@ public:
virtual void get_string_delimiters(List<String> *p_delimiters) const override;
virtual bool is_using_templates() override;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
- virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override;
+ virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override;
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override;
virtual Script *create_script() const override;
#ifndef DISABLE_DEPRECATED
@@ -562,11 +567,6 @@ public:
virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) override;
virtual void remove_named_global_constant(const StringName &p_name) override;
- /* MULTITHREAD FUNCTIONS */
-
- virtual void thread_enter() override;
- virtual void thread_exit() override;
-
/* DEBUGGER FUNCTIONS */
virtual String debug_get_error() const override;
@@ -581,6 +581,7 @@ public:
virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual void reload_all_scripts() override;
+ virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) override;
virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
virtual void frame() override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 5478a46bbc..8584a44493 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -31,6 +31,7 @@
#include "gdscript_analyzer.h"
#include "gdscript.h"
+#include "gdscript_utility_callable.h"
#include "gdscript_utility_functions.h"
#include "core/config/engine.h"
@@ -845,7 +846,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return result;
}
-void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source) {
+void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source) {
ERR_FAIL_COND(!p_class->has_member(p_name));
resolve_class_member(p_class, p_class->members_indices[p_name], p_source);
}
@@ -3410,8 +3411,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
#ifdef SUGGEST_GODOT4_RENAMES
- String rename_hint = String();
- if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+ String rename_hint;
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
if (renamed_function_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
@@ -3620,8 +3621,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
#ifdef SUGGEST_GODOT4_RENAMES
- String rename_hint = String();
- if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+ String rename_hint;
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
@@ -3636,7 +3637,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
switch (base.builtin_type) {
case Variant::NIL: {
if (base.is_hard_type()) {
- push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier);
+ push_error(vformat(R"(Cannot get property "%s" on a null object.)", name), p_identifier);
}
return;
}
@@ -3658,10 +3659,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
return;
}
}
+ if (Variant::has_builtin_method(base.builtin_type, name)) {
+ p_identifier->set_datatype(make_callable_type(Variant::get_builtin_method_info(base.builtin_type, name)));
+ return;
+ }
if (base.is_hard_type()) {
#ifdef SUGGEST_GODOT4_RENAMES
- String rename_hint = String();
- if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+ String rename_hint;
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
@@ -4113,6 +4118,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
return;
}
+ if (Variant::has_utility_function(name) || GDScriptUtilityFunctions::function_exists(name)) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = Callable(memnew(GDScriptUtilityCallable(name)));
+ MethodInfo method_info;
+ if (GDScriptUtilityFunctions::function_exists(name)) {
+ method_info = GDScriptUtilityFunctions::get_function_info(name);
+ } else {
+ method_info = Variant::get_utility_function_info(name);
+ }
+ p_identifier->set_datatype(make_callable_type(method_info));
+ return;
+ }
+
// Allow "Variant" here since it might be used for nested enums.
if (can_be_builtin && name == SNAME("Variant")) {
GDScriptParser::DataType variant;
@@ -4125,23 +4143,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
// Not found.
- // Check if it's a builtin function.
- if (GDScriptUtilityFunctions::function_exists(name)) {
- push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
- } else {
#ifdef SUGGEST_GODOT4_RENAMES
- String rename_hint = String();
- if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
- const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
- if (renamed_identifier_name) {
- rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
- }
+ String rename_hint;
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+ const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+ if (renamed_identifier_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
- push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
+ }
+ push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
#else
- push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+ push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
#endif // SUGGEST_GODOT4_RENAMES
- }
GDScriptParser::DataType dummy;
dummy.kind = GDScriptParser::DataType::VARIANT;
p_identifier->set_datatype(dummy); // Just so type is set to something.
@@ -4904,8 +4917,19 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
result.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
- result.kind = GDScriptParser::DataType::NATIVE;
- result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name;
+ if (ScriptServer::is_global_class(p_property.class_name)) {
+ result.kind = GDScriptParser::DataType::SCRIPT;
+ result.script_path = ScriptServer::get_global_class_path(p_property.class_name);
+ result.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
+
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name));
+ if (scr.is_valid()) {
+ result.script_type = scr;
+ }
+ } else {
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ }
} else {
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = p_property.type;
@@ -5311,8 +5335,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
return result;
}
-// TODO: Add safe/unsafe return variable (for variant cases)
bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
+#ifdef DEBUG_ENABLED
+ if (p_source_node) {
+ if (p_target.kind == GDScriptParser::DataType::ENUM) {
+ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
+ parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
+ }
+ }
+ }
+#endif
+ return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node);
+}
+
+// TODO: Add safe/unsafe return variable (for variant cases)
+bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
// These return "true" so it doesn't affect users negatively.
ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
@@ -5347,11 +5384,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
if (p_target.kind == GDScriptParser::DataType::ENUM) {
if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
-#ifdef DEBUG_ENABLED
- if (p_source_node) {
- parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
- }
-#endif
return true;
}
if (p_source.kind == GDScriptParser::DataType::ENUM) {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index ec155706df..e398ccfdbb 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -62,7 +62,7 @@ class GDScriptAnalyzer {
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
- void resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source = nullptr);
+ void resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive);
@@ -147,6 +147,7 @@ public:
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers();
+ static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptAnalyzer(GDScriptParser *p_parser);
};
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 9bface6136..f902cb10cc 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -121,7 +121,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
RBMap<MethodBind *, int> method_bind_map;
RBMap<GDScriptFunction *, int> lambdas_map;
-#if DEBUG_ENABLED
+#ifdef DEBUG_ENABLED
// Keep method and property names for pointer and validated operations.
// Used when disassembling the bytecode.
Vector<String> operator_names;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 76f4e69ab9..ef783ab564 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -67,10 +67,15 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
while (p_new_status > status) {
switch (status) {
- case EMPTY:
+ case EMPTY: {
status = PARSED;
- result = parser->parse(GDScriptCache::get_source_code(path), path, false);
- break;
+ String remapped_path = ResourceLoader::path_remap(path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ result = parser->parse_binary(GDScriptCache::get_binary_tokens(remapped_path), path);
+ } else {
+ result = parser->parse(GDScriptCache::get_source_code(remapped_path), path, false);
+ }
+ } break;
case PARSED: {
status = INHERITANCE_SOLVED;
Error inheritance_result = get_analyzer()->resolve_inheritance();
@@ -205,7 +210,8 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
return ref;
}
} else {
- if (!FileAccess::exists(p_path)) {
+ String remapped_path = ResourceLoader::path_remap(p_path);
+ if (!FileAccess::exists(remapped_path)) {
r_error = ERR_FILE_NOT_FOUND;
return ref;
}
@@ -239,6 +245,20 @@ String GDScriptCache::get_source_code(const String &p_path) {
return source;
}
+Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
+ Vector<uint8_t> buffer;
+ Error err = OK;
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, buffer, "Failed to open binary GDScript file '" + p_path + "'.");
+
+ uint64_t len = f->get_length();
+ buffer.resize(len);
+ uint64_t read = f->get_buffer(buffer.ptrw(), buffer.size());
+ ERR_FAIL_COND_V_MSG(read != len, Vector<uint8_t>(), "Failed to read binary GDScript file '" + p_path + "'.");
+
+ return buffer;
+}
+
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
@@ -251,10 +271,20 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
+ String remapped_path = ResourceLoader::path_remap(p_path);
+
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
- r_error = script->load_source_code(p_path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
+ if (buffer.is_empty()) {
+ r_error = ERR_FILE_CANT_READ;
+ }
+ script->set_binary_tokens_source(buffer);
+ } else {
+ r_error = script->load_source_code(remapped_path);
+ }
if (r_error) {
return Ref<GDScript>(); // Returns null and does not cache when the script fails to load.
@@ -294,9 +324,18 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
if (p_update_from_disk) {
- r_error = script->load_source_code(p_path);
- if (r_error) {
- return script;
+ if (p_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(p_path);
+ if (buffer.is_empty()) {
+ r_error = ERR_FILE_CANT_READ;
+ return script;
+ }
+ script->set_binary_tokens_source(buffer);
+ } else {
+ r_error = script->load_source_code(p_path);
+ if (r_error) {
+ return script;
+ }
}
}
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 0a0f403e44..0754e9feb6 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -99,6 +99,7 @@ public:
static void remove_script(const String &p_path);
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path);
+ static Vector<uint8_t> get_binary_tokens(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
static Ref<GDScript> get_cached_script(const String &p_path);
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 9560f670e6..13ed66710c 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -37,6 +37,7 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
+#include "core/core_string_names.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
if (codegen.function_node && codegen.function_node->is_static) {
@@ -322,9 +323,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
// Get like it was a property.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
- GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
- gen->write_get_named(temp, identifier, self);
+ GDScriptCodeGenerator::Address base(GDScriptCodeGenerator::Address::SELF);
+ if (member.type == GDScriptParser::ClassNode::Member::FUNCTION && member.function->is_static) {
+ base = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS);
+ }
+
+ gen->write_get_named(temp, identifier, base);
return temp;
}
}
@@ -341,7 +346,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
scr = scr->_base;
}
- if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) {
+ if (nc && (identifier == CoreStringNames::get_singleton()->_free || ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) {
// Get like it was a property.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
@@ -1371,7 +1376,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return GDScriptCodeGenerator::Address();
}
- main_script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self });
+ codegen.script->lambda_info.insert(function, { (int)lambda->captures.size(), lambda->use_self });
gen->write_lambda(result, function, captures, lambda->use_self);
for (int i = 0; i < captures.size(); i++) {
@@ -3046,7 +3051,7 @@ GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement
FunctionLambdaInfo info;
info.function = p_func;
info.parent = p_parent_func;
- info.script = p_parent_func;
+ info.script = p_func->get_script();
info.name = p_func->get_name();
info.line = p_func->_initial_line;
info.index = p_index;
@@ -3057,10 +3062,14 @@ GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement
info.default_arg_count = p_func->_default_arg_count;
info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func);
- GDScript::LambdaInfo *extra_info = main_script->lambda_info.getptr(p_func);
+ ERR_FAIL_NULL_V(info.script, info);
+ GDScript::LambdaInfo *extra_info = info.script->lambda_info.getptr(p_func);
if (extra_info != nullptr) {
info.capture_count = extra_info->capture_count;
info.use_self = extra_info->use_self;
+ } else {
+ info.capture_count = 0;
+ info.use_self = false;
}
return info;
@@ -3195,22 +3204,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements;
_get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info);
-
- {
- MutexLock outer_lock(main_script->func_ptrs_to_update_mutex);
- for (GDScript::UpdatableFuncPtr *updatable : main_script->func_ptrs_to_update) {
- MutexLock inner_lock(updatable->mutex);
- for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) {
- GDScriptFunction **replacement = func_ptr_replacements.getptr(*func_ptr_ptr);
- if (replacement != nullptr) {
- *func_ptr_ptr = *replacement;
- } else {
- // Probably a lambda from another reload, ignore.
- *func_ptr_ptr = nullptr;
- }
- }
- }
- }
+ main_script->_recurse_replace_function_ptrs(func_ptr_replacements);
if (has_static_data && !root->annotated_static_unload) {
GDScriptCache::add_static_script(p_script);
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index fd6b22f527..0adbe1ed8e 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -45,19 +45,19 @@ class GDScriptCompiler {
GDScript *main_script = nullptr;
struct FunctionLambdaInfo {
- GDScriptFunction *function;
- GDScriptFunction *parent;
- Ref<GDScript> script;
+ GDScriptFunction *function = nullptr;
+ GDScriptFunction *parent = nullptr;
+ GDScript *script = nullptr;
StringName name;
- int line;
- int index;
- int depth;
+ int line = 0;
+ int index = 0;
+ int depth = 0;
//uint64_t code_hash;
//int code_size;
- int capture_count;
- int use_self;
- int arg_count;
- int default_arg_count;
+ int capture_count = 0;
+ bool use_self = false;
+ int arg_count = 0;
+ int default_arg_count = 0;
//Vector<GDScriptDataType> argument_types;
//GDScriptDataType return_type;
Vector<FunctionLambdaInfo> sublambdas;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 87617cafab..babd2c1772 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -104,7 +104,7 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
return scr;
}
-Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) {
+Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(const StringName &p_object) {
Vector<ScriptLanguage::ScriptTemplate> templates;
#ifdef TOOLS_ENABLED
for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) {
@@ -210,7 +210,7 @@ bool GDScriptLanguage::supports_documentation() const {
}
int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
int indent = 0;
GDScriptTokenizer::Token current = tokenizer.scan();
@@ -537,7 +537,7 @@ struct GDScriptCompletionIdentifier {
// appears. For example, if you are completing code in a class that inherits Node2D, a property found on Node2D
// will have a "better" (lower) location "score" than a property that is found on CanvasItem.
-static int _get_property_location(StringName p_class, StringName p_property) {
+static int _get_property_location(const StringName &p_class, const StringName &p_property) {
if (!ClassDB::has_property(p_class, p_property)) {
return ScriptLanguage::LOCATION_OTHER;
}
@@ -552,7 +552,20 @@ static int _get_property_location(StringName p_class, StringName p_property) {
return depth | ScriptLanguage::LOCATION_PARENT_MASK;
}
-static int _get_constant_location(StringName p_class, StringName p_constant) {
+static int _get_property_location(Ref<Script> p_script, const StringName &p_property) {
+ int depth = 0;
+ Ref<Script> scr = p_script;
+ while (scr.is_valid()) {
+ if (scr->get_member_line(p_property) != -1) {
+ return depth | ScriptLanguage::LOCATION_PARENT_MASK;
+ }
+ depth++;
+ scr = scr->get_base_script();
+ }
+ return depth + _get_property_location(p_script->get_instance_base_type(), p_property);
+}
+
+static int _get_constant_location(const StringName &p_class, const StringName &p_constant) {
if (!ClassDB::has_integer_constant(p_class, p_constant)) {
return ScriptLanguage::LOCATION_OTHER;
}
@@ -567,7 +580,20 @@ static int _get_constant_location(StringName p_class, StringName p_constant) {
return depth | ScriptLanguage::LOCATION_PARENT_MASK;
}
-static int _get_signal_location(StringName p_class, StringName p_signal) {
+static int _get_constant_location(Ref<Script> p_script, const StringName &p_constant) {
+ int depth = 0;
+ Ref<Script> scr = p_script;
+ while (scr.is_valid()) {
+ if (scr->get_member_line(p_constant) != -1) {
+ return depth | ScriptLanguage::LOCATION_PARENT_MASK;
+ }
+ depth++;
+ scr = scr->get_base_script();
+ }
+ return depth + _get_constant_location(p_script->get_instance_base_type(), p_constant);
+}
+
+static int _get_signal_location(const StringName &p_class, const StringName &p_signal) {
if (!ClassDB::has_signal(p_class, p_signal)) {
return ScriptLanguage::LOCATION_OTHER;
}
@@ -582,7 +608,20 @@ static int _get_signal_location(StringName p_class, StringName p_signal) {
return depth | ScriptLanguage::LOCATION_PARENT_MASK;
}
-static int _get_method_location(StringName p_class, StringName p_method) {
+static int _get_signal_location(Ref<Script> p_script, const StringName &p_signal) {
+ int depth = 0;
+ Ref<Script> scr = p_script;
+ while (scr.is_valid()) {
+ if (scr->get_member_line(p_signal) != -1) {
+ return depth | ScriptLanguage::LOCATION_PARENT_MASK;
+ }
+ depth++;
+ scr = scr->get_base_script();
+ }
+ return depth + _get_signal_location(p_script->get_instance_base_type(), p_signal);
+}
+
+static int _get_method_location(const StringName &p_class, const StringName &p_method) {
if (!ClassDB::has_method(p_class, p_method)) {
return ScriptLanguage::LOCATION_OTHER;
}
@@ -597,7 +636,7 @@ static int _get_method_location(StringName p_class, StringName p_method) {
return depth | ScriptLanguage::LOCATION_PARENT_MASK;
}
-static int _get_enum_constant_location(StringName p_class, StringName p_enum_constant) {
+static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) {
if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) {
return ScriptLanguage::LOCATION_OTHER;
}
@@ -620,9 +659,9 @@ static String _trim_parent_class(const String &p_class, const String &p_base_cla
}
Vector<String> names = p_class.split(".", false, 1);
if (names.size() == 2) {
- String first = names[0];
- String rest = names[1];
+ const String &first = names[0];
if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) {
+ const String &rest = names[1];
return rest;
}
}
@@ -739,18 +778,24 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer);
if (call->is_constant && call->reduced) {
def_val = call->function_name.operator String() + call->reduced_value.operator String();
+ } else {
+ def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
}
} break;
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer);
if (arr->is_constant && arr->reduced) {
def_val = arr->reduced_value.operator String();
+ } else {
+ def_val = arr->elements.is_empty() ? "[]" : "[...]";
}
} break;
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer);
if (dict->is_constant && dict->reduced) {
def_val = dict->reduced_value.operator String();
+ } else {
+ def_val = dict->elements.is_empty() ? "{}" : "{...}";
}
} break;
case GDScriptParser::Node::SUBSCRIPT: {
@@ -968,9 +1013,9 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite,
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth);
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, bool p_types_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth);
-static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
+static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_types_only, bool p_static, bool p_parent_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
if (!p_parent_only) {
@@ -984,13 +1029,13 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
ScriptLanguage::CodeCompletionOption option;
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE:
- if (p_only_functions || outer || (p_static && !member.variable->is_static)) {
+ if (p_types_only || p_only_functions || outer || (p_static && !member.variable->is_static)) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
break;
case GDScriptParser::ClassNode::Member::CONSTANT:
- if (p_only_functions) {
+ if (p_types_only || p_only_functions) {
continue;
}
if (r_result.has(member.constant->identifier->name)) {
@@ -1008,7 +1053,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option = ScriptLanguage::CodeCompletionOption(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, location);
break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- if (p_only_functions) {
+ if (p_types_only || p_only_functions) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.enum_value.identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
@@ -1020,7 +1065,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option = ScriptLanguage::CodeCompletionOption(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location);
break;
case GDScriptParser::ClassNode::Member::FUNCTION:
- if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) {
+ if (p_types_only || outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
@@ -1031,7 +1076,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
}
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
- if (p_only_functions || outer || p_static) {
+ if (p_types_only || p_only_functions || outer || p_static) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location);
@@ -1043,6 +1088,10 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
}
r_result.insert(option.display, option);
}
+ if (p_types_only) {
+ break; // Otherwise, it will fill the results with types from the outer class (which is undesired for that case).
+ }
+
outer = true;
clss = clss->outer;
classes_processed++;
@@ -1054,15 +1103,15 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
base_type.type = p_class->base_type;
base_type.type.is_meta_type = p_static;
- _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1);
+ _find_identifiers_in_base(base_type, p_only_functions, p_types_only, r_result, p_recursion_depth + 1);
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, bool p_types_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
GDScriptParser::DataType base_type = p_base.type;
- if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
+ if (!p_types_only && base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL);
option.insert_text += "(";
r_result.insert(option.display, option);
@@ -1071,25 +1120,27 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, p_types_only, base_type.is_meta_type, false, r_result, p_recursion_depth);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
- if (!p_only_functions) {
+ if (p_types_only) {
+ // TODO: Need to implement Script::get_script_enum_list and retrieve the enum list from a script.
+ } else if (!p_only_functions) {
if (!base_type.is_meta_type) {
List<PropertyInfo> members;
scr->get_script_property_list(&members);
for (const PropertyInfo &E : members) {
- if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) {
+ if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) {
continue;
}
if (E.name.contains("/")) {
continue;
}
- int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name);
+ int location = p_recursion_depth + _get_property_location(scr, E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
r_result.insert(option.display, option);
}
@@ -1097,7 +1148,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
List<MethodInfo> signals;
scr->get_script_signal_list(&signals);
for (const MethodInfo &E : signals) {
- int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name);
+ int location = p_recursion_depth + _get_signal_location(scr, E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location);
r_result.insert(option.display, option);
}
@@ -1105,26 +1156,28 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
HashMap<StringName, Variant> constants;
scr->get_constants(&constants);
for (const KeyValue<StringName, Variant> &E : constants) {
- int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key);
+ int location = p_recursion_depth + _get_constant_location(scr, E.key);
ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
r_result.insert(option.display, option);
}
}
- List<MethodInfo> methods;
- scr->get_script_method_list(&methods);
- for (const MethodInfo &E : methods) {
- if (E.name.begins_with("@")) {
- continue;
- }
- int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name);
- ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
- if (E.arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
+ if (!p_types_only) {
+ List<MethodInfo> methods;
+ scr->get_script_method_list(&methods);
+ for (const MethodInfo &E : methods) {
+ if (E.name.begins_with("@")) {
+ continue;
+ }
+ int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name);
+ ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
+ if (E.arguments.size()) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
}
- r_result.insert(option.display, option);
}
Ref<Script> base_script = scr->get_base_script();
@@ -1145,6 +1198,16 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
return;
}
+ if (p_types_only) {
+ List<StringName> enums;
+ ClassDB::get_enum_list(type, &enums);
+ for (const StringName &E : enums) {
+ ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM);
+ r_result.insert(option.display, option);
+ }
+ return;
+ }
+
if (!p_only_functions) {
List<String> constants;
ClassDB::get_integer_constant_list(type, &constants);
@@ -1158,7 +1221,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
List<PropertyInfo> pinfo;
ClassDB::get_property_list(type, &pinfo);
for (const PropertyInfo &E : pinfo) {
- if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) {
+ if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) {
continue;
}
if (E.name.contains("/")) {
@@ -1203,6 +1266,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
} break;
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::BUILTIN: {
+ if (p_types_only) {
+ return;
+ }
+
Callable::CallError err;
Variant tmp;
Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err);
@@ -1210,6 +1277,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
return;
}
+ int location = ScriptLanguage::LOCATION_OTHER;
+
if (!p_only_functions) {
List<PropertyInfo> members;
if (p_base.value.get_type() != Variant::NIL) {
@@ -1219,11 +1288,15 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
for (const PropertyInfo &E : members) {
- if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) {
+ if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) {
continue;
}
if (!String(E.name).contains("/")) {
- ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER);
+ ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
+ if (base_type.kind == GDScriptParser::DataType::ENUM) {
+ // Sort enum members in their declaration order.
+ location += 1;
+ }
if (GDScriptParser::theme_color_names.has(E.name)) {
option.theme_color_name = GDScriptParser::theme_color_names[E.name];
}
@@ -1239,7 +1312,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
// Enum types are static and cannot change, therefore we skip non-const dictionary methods.
continue;
}
- ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
if (E.arguments.size()) {
option.insert_text += "(";
} else {
@@ -1264,7 +1337,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
if (p_context.current_class) {
- _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, false, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
}
List<StringName> functions;
@@ -1364,7 +1437,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
}
-static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
+static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, GDScriptParser::CompletionContext &p_context) {
GDScriptCompletionIdentifier ci;
ci.value = p_value;
ci.type.is_constant = true;
@@ -1386,8 +1459,22 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
scr = obj->get_script();
}
if (scr.is_valid()) {
- ci.type.script_type = scr;
+ ci.type.script_path = scr->get_path();
+
+ if (scr->get_path().ends_with(".gd")) {
+ Error err;
+ Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err);
+ if (err == OK) {
+ ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ ci.type.class_type = parser->get_parser()->get_tree();
+ ci.type.kind = GDScriptParser::DataType::CLASS;
+ p_context.dependent_parsers.push_back(parser);
+ return ci;
+ }
+ }
+
ci.type.kind = GDScriptParser::DataType::SCRIPT;
+ ci.type.script_type = scr;
ci.type.native_type = scr->get_instance_base_type();
} else {
ci.type.kind = GDScriptParser::DataType::NATIVE;
@@ -1412,8 +1499,19 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr
ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
ci.type.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
- ci.type.kind = GDScriptParser::DataType::NATIVE;
- ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ if (ScriptServer::is_global_class(p_property.class_name)) {
+ ci.type.kind = GDScriptParser::DataType::SCRIPT;
+ ci.type.script_path = ScriptServer::get_global_class_path(p_property.class_name);
+ ci.type.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
+
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name));
+ if (scr.is_valid()) {
+ ci.type.script_type = scr;
+ }
+ } else {
+ ci.type.kind = GDScriptParser::DataType::NATIVE;
+ ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ }
} else {
ci.type.kind = GDScriptParser::DataType::BUILTIN;
}
@@ -1475,7 +1573,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (p_expression->is_constant) {
// Already has a value, so just use that.
- r_type = _type_from_variant(p_expression->reduced_value);
+ r_type = _type_from_variant(p_expression->reduced_value, p_context);
switch (p_expression->get_datatype().kind) {
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::CLASS:
@@ -1489,16 +1587,13 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
switch (p_expression->type) {
case GDScriptParser::Node::LITERAL: {
const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression);
- r_type = _type_from_variant(literal->value);
+ r_type = _type_from_variant(literal->value, p_context);
found = true;
} break;
case GDScriptParser::Node::SELF: {
if (p_context.current_class) {
- if (p_context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
- r_type.type = p_context.current_class->get_datatype();
- } else {
- r_type.type = p_context.current_class->base_type;
- }
+ r_type.type = p_context.current_class->get_datatype();
+ r_type.type.is_meta_type = false;
found = true;
}
} break;
@@ -1673,7 +1768,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (!which.is_empty()) {
// Try singletons first
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context);
found = true;
} else {
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
@@ -1724,7 +1819,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
- r_type = _type_from_variant(ret);
+ r_type = _type_from_variant(ret, p_context);
found = true;
}
}
@@ -1752,7 +1847,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) {
Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)];
- r_type = _type_from_variant(value);
+ r_type = _type_from_variant(value, p_context);
found = true;
break;
}
@@ -1804,7 +1899,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (base.value.in(index.value)) {
Variant value = base.value.get(index.value);
- r_type = _type_from_variant(value);
+ r_type = _type_from_variant(value, p_context);
found = true;
break;
}
@@ -1859,7 +1954,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
bool valid = false;
Variant res = base_val.get(index.value, &valid);
if (valid) {
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
r_type.value = Variant();
r_type.type.is_constant = false;
found = true;
@@ -1917,7 +2012,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
found = false;
break;
}
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
if (!v1_use_value || !v2_use_value) {
r_type.value = Variant();
r_type.type.is_constant = false;
@@ -2006,6 +2101,21 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
default:
break;
}
+ } else {
+ if (p_context.current_class) {
+ GDScriptCompletionIdentifier base_identifier;
+
+ GDScriptCompletionIdentifier base;
+ base.value = p_context.base;
+ base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ base.type.kind = GDScriptParser::DataType::CLASS;
+ base.type.class_type = p_context.current_class;
+ base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
+
+ if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) {
+ id_type = base_identifier.type;
+ }
+ }
}
while (suite) {
@@ -2059,8 +2169,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
if (last_assigned_expression && last_assign_line < p_context.current_line) {
GDScriptParser::CompletionContext c = p_context;
c.current_line = last_assign_line;
- r_type.assigned_expression = last_assigned_expression;
- if (_guess_expression_type(c, last_assigned_expression, r_type)) {
+ GDScriptCompletionIdentifier assigned_type;
+ if (_guess_expression_type(c, last_assigned_expression, assigned_type)) {
+ if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) {
+ // The assigned type is incompatible. The annotated type takes priority.
+ r_type.assigned_expression = last_assigned_expression;
+ r_type.type = id_type;
+ } else {
+ r_type = assigned_type;
+ }
return true;
}
}
@@ -2118,20 +2235,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
return true;
}
- // Check current class (including inheritance).
- if (p_context.current_class) {
- GDScriptCompletionIdentifier base;
- base.value = p_context.base;
- base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- base.type.kind = GDScriptParser::DataType::CLASS;
- base.type.class_type = p_context.current_class;
- base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
-
- if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) {
- return true;
- }
- }
-
// Check global scripts.
if (ScriptServer::is_global_class(p_identifier->name)) {
String script = ScriptServer::get_global_class_path(p_identifier->name);
@@ -2152,7 +2255,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
} else {
Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name));
if (scr.is_valid()) {
- r_type = _type_from_variant(scr);
+ r_type = _type_from_variant(scr, p_context);
r_type.type.is_meta_type = true;
return true;
}
@@ -2162,7 +2265,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
// Check global variables (including autoloads).
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]);
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name], p_context);
return true;
}
@@ -2215,7 +2318,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
const GDScriptParser::ExpressionNode *init = member.variable->initializer;
if (init->is_constant) {
r_type.value = init->reduced_value;
- r_type = _type_from_variant(init->reduced_value);
+ r_type = _type_from_variant(init->reduced_value, p_context);
return true;
} else if (init->start_line == p_context.current_line) {
return false;
@@ -2242,7 +2345,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
r_type.enumeration = member.m_enum->identifier->name;
return true;
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- r_type = _type_from_variant(member.enum_value.value);
+ r_type = _type_from_variant(member.enum_value.value, p_context);
return true;
case GDScriptParser::ClassNode::Member::SIGNAL:
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -2278,7 +2381,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
HashMap<StringName, Variant> constants;
scr->get_constants(&constants);
if (constants.has(p_identifier)) {
- r_type = _type_from_variant(constants[p_identifier]);
+ r_type = _type_from_variant(constants[p_identifier], p_context);
return true;
}
@@ -2342,7 +2445,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
bool valid = false;
Variant res = tmp.get(p_identifier, &valid);
if (valid) {
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
r_type.value = Variant();
r_type.type.is_constant = false;
return true;
@@ -3069,7 +3172,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
- _find_identifiers_in_base(base, is_function, options, 0);
+ _find_identifiers_in_base(base, is_function, false, options, 0);
}
} break;
case GDScriptParser::COMPLETION_SUBSCRIPT: {
@@ -3079,7 +3182,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
- _find_identifiers_in_base(base, false, options, 0);
+ _find_identifiers_in_base(base, false, false, options, 0);
} break;
case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
if (!completion_context.current_class) {
@@ -3087,25 +3190,41 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node);
bool found = true;
+
GDScriptCompletionIdentifier base;
base.type.kind = GDScriptParser::DataType::CLASS;
base.type.type_source = GDScriptParser::DataType::INFERRED;
base.type.is_constant = true;
- base.type.class_type = completion_context.current_class;
- base.value = completion_context.base;
- for (int i = 0; i < completion_context.current_argument; i++) {
- GDScriptCompletionIdentifier ci;
- if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
- found = false;
- break;
+ if (completion_context.current_argument == 1) {
+ StringName type_name = type->type_chain[0]->name;
+
+ if (ClassDB::class_exists(type_name)) {
+ base.type.kind = GDScriptParser::DataType::NATIVE;
+ base.type.native_type = type_name;
+ } else if (ScriptServer::is_global_class(type_name)) {
+ base.type.kind = GDScriptParser::DataType::SCRIPT;
+ String scr_path = ScriptServer::get_global_class_path(type_name);
+ base.type.script_type = ResourceLoader::load(scr_path);
+ }
+ }
+
+ if (base.type.kind == GDScriptParser::DataType::CLASS) {
+ base.type.class_type = completion_context.current_class;
+ base.value = completion_context.base;
+
+ for (int i = 0; i < completion_context.current_argument; i++) {
+ GDScriptCompletionIdentifier ci;
+ if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
+ found = false;
+ break;
+ }
+ base = ci;
}
- base = ci;
}
- // TODO: Improve this to only list types.
if (found) {
- _find_identifiers_in_base(base, false, options, 0);
+ _find_identifiers_in_base(base, false, true, options, 0);
}
r_forced = true;
} break;
@@ -3194,6 +3313,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<String> opts;
p_owner->get_argument_options("get_node", 0, &opts);
+ bool for_unique_name = false;
+ if (completion_context.node != nullptr && completion_context.node->type == GDScriptParser::Node::GET_NODE && !static_cast<GDScriptParser::GetNodeNode *>(completion_context.node)->use_dollar) {
+ for_unique_name = true;
+ }
+
for (const String &E : opts) {
r_forced = true;
String opt = E.strip_edges();
@@ -3202,9 +3326,25 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
// or handle NodePaths which are valid identifiers and don't need quotes.
opt = opt.unquote();
}
- // The path needs quotes if it's not a valid identifier (with an exception
- // for "/" as path separator, which also doesn't require quotes).
- if (!opt.replace("/", "_").is_valid_identifier()) {
+
+ if (for_unique_name) {
+ if (!opt.begins_with("%")) {
+ continue;
+ }
+ opt = opt.substr(1);
+ }
+
+ // The path needs quotes if at least one of its components (excluding `/` separations)
+ // is not a valid identifier.
+ bool path_needs_quote = false;
+ for (const String &part : opt.split("/")) {
+ if (!part.is_valid_identifier()) {
+ path_needs_quote = true;
+ break;
+ }
+ }
+
+ if (path_needs_quote) {
// Ignore quote_style and just use double quotes for paths with apostrophes.
// Double quotes don't need to be checked because they're not valid in node and property names.
opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference.
@@ -3213,11 +3353,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
options.insert(option.display, option);
}
- // Get autoloads.
- for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
- String path = "/root/" + E.key;
- ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
- options.insert(option.display, option);
+ if (!for_unique_name) {
+ // Get autoloads.
+ for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
+ String path = "/root/" + E.key;
+ ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
+ options.insert(option.display, option);
+ }
}
}
} break;
@@ -3225,7 +3367,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
if (!completion_context.current_class) {
break;
}
- _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0);
+ _find_identifiers_in_class(completion_context.current_class, true, false, false, true, options, 0);
} break;
}
@@ -3411,6 +3553,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
if (ClassDB::has_property(class_name, p_symbol, true)) {
+ PropertyInfo prop_info;
+ ClassDB::get_property_info(class_name, p_symbol, &prop_info, true);
+ if (prop_info.usage & PROPERTY_USAGE_INTERNAL) {
+ return ERR_CANT_RESOLVE;
+ }
+
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
r_result.class_name = base_type.native_type;
r_result.class_member = p_symbol;
@@ -3524,14 +3672,50 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
analyzer.analyze();
if (context.current_class && context.current_class->extends.size() > 0) {
+ StringName class_name = context.current_class->extends[0]->name;
+
bool success = false;
- ClassDB::get_integer_constant(context.current_class->extends[0]->name, p_symbol, &success);
+ ClassDB::get_integer_constant(class_name, p_symbol, &success);
if (success) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
- r_result.class_name = context.current_class->extends[0]->name;
+ r_result.class_name = class_name;
r_result.class_member = p_symbol;
return OK;
}
+ do {
+ List<StringName> enums;
+ ClassDB::get_enum_list(class_name, &enums, true);
+ for (const StringName &enum_name : enums) {
+ if (enum_name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ r_result.class_name = class_name;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+ class_name = ClassDB::get_parent_class_nocheck(class_name);
+ } while (class_name != StringName());
+ }
+
+ const GDScriptParser::TypeNode *type_node = dynamic_cast<const GDScriptParser::TypeNode *>(context.node);
+ if (type_node != nullptr && !type_node->type_chain.is_empty()) {
+ StringName class_name = type_node->type_chain[0]->name;
+ if (ScriptServer::is_global_class(class_name)) {
+ class_name = ScriptServer::get_global_class_native_base(class_name);
+ }
+ do {
+ List<StringName> enums;
+ ClassDB::get_enum_list(class_name, &enums, true);
+ for (const StringName &enum_name : enums) {
+ if (enum_name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ r_result.class_name = class_name;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+ class_name = ClassDB::get_parent_class_nocheck(class_name);
+ } while (class_name != StringName());
}
bool is_function = false;
@@ -3561,7 +3745,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
case GDScriptParser::COMPLETION_ASSIGN:
case GDScriptParser::COMPLETION_CALL_ARGUMENTS:
case GDScriptParser::COMPLETION_IDENTIFIER:
- case GDScriptParser::COMPLETION_PROPERTY_METHOD: {
+ case GDScriptParser::COMPLETION_PROPERTY_METHOD:
+ case GDScriptParser::COMPLETION_SUBSCRIPT: {
GDScriptParser::DataType base_type;
if (context.current_class) {
if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 547f5607d3..f6fa17c84f 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -139,20 +139,14 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
}
}
-GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
+ function(p_function) {
ERR_FAIL_NULL(p_script.ptr());
ERR_FAIL_NULL(p_function);
script = p_script;
- function = p_function;
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
-
- updatable_func_ptr_element = p_script->_add_func_ptr_to_update(&function);
-}
-
-GDScriptLambdaCallable::~GDScriptLambdaCallable() {
- GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
}
bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
@@ -264,37 +258,23 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
}
}
-GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
+ function(p_function) {
ERR_FAIL_NULL(p_self.ptr());
ERR_FAIL_NULL(p_function);
reference = p_self;
object = p_self.ptr();
- function = p_function;
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
-
- GDScript *gds = p_function->get_script();
- if (gds != nullptr) {
- updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function);
- }
}
-GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
+ function(p_function) {
ERR_FAIL_NULL(p_self);
ERR_FAIL_NULL(p_function);
object = p_self;
- function = p_function;
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
-
- GDScript *gds = p_function->get_script();
- if (gds != nullptr) {
- updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function);
- }
-}
-
-GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() {
- GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
}
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index ee7d547544..2c5d01aa16 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -42,10 +42,9 @@ class GDScriptFunction;
class GDScriptInstance;
class GDScriptLambdaCallable : public CallableCustom {
- GDScriptFunction *function = nullptr;
+ GDScript::UpdatableFuncPtr function;
Ref<GDScript> script;
uint32_t h;
- GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
Vector<Variant> captures;
@@ -62,17 +61,18 @@ public:
StringName get_method() const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+ GDScriptLambdaCallable(GDScriptLambdaCallable &) = delete;
+ GDScriptLambdaCallable(const GDScriptLambdaCallable &) = delete;
GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
- virtual ~GDScriptLambdaCallable();
+ virtual ~GDScriptLambdaCallable() = default;
};
// Lambda callable that references a particular object, so it can use `self` in the body.
class GDScriptLambdaSelfCallable : public CallableCustom {
- GDScriptFunction *function = nullptr;
+ GDScript::UpdatableFuncPtr function;
Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference.
Object *object = nullptr; // For non RefCounted objects, use a direct pointer.
uint32_t h;
- GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
Vector<Variant> captures;
@@ -88,9 +88,11 @@ public:
ObjectID get_object() const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+ GDScriptLambdaSelfCallable(GDScriptLambdaSelfCallable &) = delete;
+ GDScriptLambdaSelfCallable(const GDScriptLambdaSelfCallable &) = delete;
GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
- virtual ~GDScriptLambdaSelfCallable();
+ virtual ~GDScriptLambdaSelfCallable() = default;
};
#endif // GDSCRIPT_LAMBDA_CALLABLE_H
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index f3a4f2eaa6..649bd735c6 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -31,6 +31,7 @@
#include "gdscript_parser.h"
#include "gdscript.h"
+#include "gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -226,7 +227,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -234,7 +235,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
completion_context = context;
@@ -244,7 +245,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -252,7 +253,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.builtin_type = p_builtin_type;
completion_context = context;
}
@@ -265,7 +266,7 @@ void GDScriptParser::push_completion_call(Node *p_call) {
call.call = p_call;
call.argument = 0;
completion_call_stack.push_back(call);
- if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+ if (previous.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_BEGINNING) {
completion_call = call;
}
}
@@ -328,17 +329,21 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
source = source.replace_first(String::chr(0xFFFF), String());
}
- tokenizer.set_source_code(source);
- tokenizer.set_cursor_position(cursor_line, cursor_column);
- script_path = p_script_path;
- current = tokenizer.scan();
+ GDScriptTokenizerText *text_tokenizer = memnew(GDScriptTokenizerText);
+ text_tokenizer->set_source_code(source);
+
+ tokenizer = text_tokenizer;
+
+ tokenizer->set_cursor_position(cursor_line, cursor_column);
+ script_path = p_script_path.simplify_path();
+ current = tokenizer->scan();
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
if (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
}
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
#ifdef DEBUG_ENABLED
@@ -359,6 +364,9 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
parse_program();
pop_multiline();
+ memdelete(text_tokenizer);
+ tokenizer = nullptr;
+
#ifdef DEBUG_ENABLED
if (multiline_stack.size() > 0) {
ERR_PRINT("Parser bug: Imbalanced multiline stack.");
@@ -372,6 +380,41 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
}
}
+Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path) {
+ GDScriptTokenizerBuffer *buffer_tokenizer = memnew(GDScriptTokenizerBuffer);
+ Error err = buffer_tokenizer->set_code_buffer(p_binary);
+
+ if (err) {
+ memdelete(buffer_tokenizer);
+ return err;
+ }
+
+ tokenizer = buffer_tokenizer;
+ script_path = p_script_path;
+ current = tokenizer->scan();
+ // Avoid error or newline as the first token.
+ // The latter can mess with the parser when opening files filled exclusively with comments and newlines.
+ while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
+ if (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ }
+ current = tokenizer->scan();
+ }
+
+ push_multiline(false); // Keep one for the whole parsing.
+ parse_program();
+ pop_multiline();
+
+ memdelete(buffer_tokenizer);
+ tokenizer = nullptr;
+
+ if (errors.is_empty()) {
+ return OK;
+ } else {
+ return ERR_PARSE_ERROR;
+ }
+}
+
GDScriptTokenizer::Token GDScriptParser::advance() {
lambda_ended = false; // Empty marker since we're past the end in any case.
@@ -379,16 +422,16 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
}
if (for_completion && !completion_call_stack.is_empty()) {
- if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
+ if (completion_call.call == nullptr && tokenizer->is_past_cursor()) {
completion_call = completion_call_stack.back()->get();
passed_cursor = true;
}
}
previous = current;
- current = tokenizer.scan();
+ current = tokenizer->scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
for (Node *n : nodes_in_progress) {
@@ -457,19 +500,19 @@ void GDScriptParser::synchronize() {
void GDScriptParser::push_multiline(bool p_state) {
multiline_stack.push_back(p_state);
- tokenizer.set_multiline_mode(p_state);
+ tokenizer->set_multiline_mode(p_state);
if (p_state) {
// Consume potential whitespace tokens already waiting in line.
while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {
- current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token.
+ current = tokenizer->scan(); // Don't call advance() here, as we don't want to change the previous token.
}
}
}
void GDScriptParser::pop_multiline() {
- ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value.");
+ ERR_FAIL_COND_MSG(multiline_stack.is_empty(), "Parser bug: trying to pop from multiline stack without available value.");
multiline_stack.pop_back();
- tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
+ tokenizer->set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
}
bool GDScriptParser::is_statement_end_token() const {
@@ -588,7 +631,7 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = MIN(max_script_doc_line, head->end_line);
while (line > 0) {
if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
@@ -597,6 +640,7 @@ void GDScriptParser::parse_program() {
}
line--;
}
+
#endif // TOOLS_ENABLED
if (!check(GDScriptTokenizer::Token::TK_EOF)) {
@@ -629,7 +673,7 @@ GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_
// Starts at index 1 because index 0 was handled above.
for (int i = 1; result != nullptr && i < class_names.size(); i++) {
- String current_name = class_names[i];
+ const String &current_name = class_names[i];
GDScriptParser::ClassNode *next = nullptr;
if (result->has_member(current_name)) {
GDScriptParser::ClassNode::Member member = result->get_member(current_name);
@@ -793,7 +837,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
if (has_comment(member->start_line, true)) {
// Inline doc comment.
member->doc_data = parse_class_doc_comment(member->start_line, true);
- } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
// This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
member->doc_data = parse_class_doc_comment(doc_comment_line);
@@ -802,7 +846,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
if (has_comment(member->start_line, true)) {
// Inline doc comment.
member->doc_data = parse_doc_comment(member->start_line, true);
- } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment.
member->doc_data = parse_doc_comment(doc_comment_line);
}
@@ -1120,7 +1164,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
case VariableNode::PROP_INLINE: {
FunctionNode *function = alloc_node<FunctionNode>();
- consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
+ if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after "get(".)*");
+ consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after "get()".)*");
+ } else {
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)");
+ }
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
@@ -1268,8 +1317,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
bool named = false;
- if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
- advance();
+ if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
enum_node->identifier = parse_identifier();
named = true;
}
@@ -1353,7 +1401,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
doc_data = parse_doc_comment(enum_value_line, true);
}
- } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment.
doc_data = parse_doc_comment(doc_comment_line);
}
@@ -2342,6 +2390,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
+ if (identifier->name.operator String().is_empty()) {
+ print_line("Empty identifier found.");
+ }
identifier->suite = current_suite;
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
@@ -3046,7 +3097,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Allow for trailing comma.
break;
}
- bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE;
+ bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
@@ -3216,7 +3267,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
// Reset the multiline stack since we don't want the multiline mode one in the lambda body.
push_multiline(false);
if (multiline_context) {
- tokenizer.push_expression_indented_block();
+ tokenizer->push_expression_indented_block();
}
push_multiline(true); // For the parameters.
@@ -3263,9 +3314,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
if (multiline_context) {
// If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.
while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) {
- current = tokenizer.scan(); // Not advance() since we don't want to change the previous token.
+ current = tokenizer->scan(); // Not advance() since we don't want to change the previous token.
}
- tokenizer.pop_expression_indented_block();
+ tokenizer->pop_expression_indented_block();
}
current_function = previous_function;
@@ -3281,6 +3332,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ // x is not int
+ // ^ ^^^ ExpressionNode, TypeNode
+ // ^^^^^^^^^^^^ TypeTestNode
+ // ^^^^^^^^^^^^ UnaryOpNode
+ UnaryOpNode *not_node = nullptr;
+ if (match(GDScriptTokenizer::Token::NOT)) {
+ not_node = alloc_node<UnaryOpNode>();
+ not_node->operation = UnaryOpNode::OP_LOGIC_NOT;
+ not_node->variant_op = Variant::OP_NOT;
+ reset_extents(not_node, p_previous_operand);
+ update_extents(not_node);
+ }
+
TypeTestNode *type_test = alloc_node<TypeTestNode>();
reset_extents(type_test, p_previous_operand);
update_extents(type_test);
@@ -3289,8 +3353,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *
type_test->test_type = parse_type();
complete_extents(type_test);
+ if (not_node != nullptr) {
+ not_node->operand = type_test;
+ complete_extents(not_node);
+ }
+
if (type_test->test_type == nullptr) {
- push_error(R"(Expected type specifier after "is".)");
+ if (not_node == nullptr) {
+ push_error(R"(Expected type specifier after "is".)");
+ } else {
+ push_error(R"(Expected type specifier after "is not".)");
+ }
+ }
+
+ if (not_node != nullptr) {
+ return not_node;
}
return type_test;
@@ -3488,20 +3565,20 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons
}
bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
- bool has_comment = tokenizer.get_comments().has(p_line);
+ bool has_comment = tokenizer->get_comments().has(p_line);
// If there are no comments or if we don't care whether the comment
// is a docstring, we have our result.
if (!p_must_be_doc || !has_comment) {
return has_comment;
}
- return tokenizer.get_comments()[p_line].comment.begins_with("##");
+ return tokenizer->get_comments()[p_line].comment.begins_with("##");
}
GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
if (!p_single_line) {
@@ -3550,7 +3627,7 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
if (!p_single_line) {
@@ -3855,12 +3932,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
- if (this->_is_tool) {
+ if (_is_tool) {
push_error(R"("@tool" annotation can only be used once.)", p_annotation);
return false;
}
#endif // DEBUG_ENABLED
- this->_is_tool = true;
+ _is_tool = true;
return true;
}
@@ -4997,6 +5074,9 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
for (const AnnotationNode *E : p_function->annotations) {
print_annotation(E);
}
+ if (p_function->is_static) {
+ push_text("Static ");
+ }
push_text(p_context);
push_text(" ");
if (p_function->identifier) {
@@ -5341,6 +5421,9 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
print_annotation(E);
}
+ if (p_variable->is_static) {
+ push_text("Static ");
+ }
push_text("Variable ");
print_identifier(p_variable->identifier);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 88b5bdc43f..c064a2d0f4 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -223,7 +223,7 @@ public:
}
bool operator!=(const DataType &p_other) const {
- return !(this->operator==(p_other));
+ return !(*this == p_other);
}
void operator=(const DataType &p_other) {
@@ -1336,7 +1336,7 @@ private:
HashSet<int> unsafe_lines;
#endif
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizer *tokenizer = nullptr;
GDScriptTokenizer::Token previous;
GDScriptTokenizer::Token current;
@@ -1540,6 +1540,7 @@ private:
public:
Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
+ Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path);
ClassNode *get_tree() const { return head; }
bool is_tool() const { return _is_tool; }
ClassNode *find_class(const String &p_qualified_name) const;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 98a3a1268f..2940af585d 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -256,7 +256,7 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
return token_names[p_token_type];
}
-void GDScriptTokenizer::set_source_code(const String &p_source_code) {
+void GDScriptTokenizerText::set_source_code(const String &p_source_code) {
source = p_source_code;
if (source.is_empty()) {
_source = U"";
@@ -270,34 +270,34 @@ void GDScriptTokenizer::set_source_code(const String &p_source_code) {
position = 0;
}
-void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) {
+void GDScriptTokenizerText::set_cursor_position(int p_line, int p_column) {
cursor_line = p_line;
cursor_column = p_column;
}
-void GDScriptTokenizer::set_multiline_mode(bool p_state) {
+void GDScriptTokenizerText::set_multiline_mode(bool p_state) {
multiline_mode = p_state;
}
-void GDScriptTokenizer::push_expression_indented_block() {
+void GDScriptTokenizerText::push_expression_indented_block() {
indent_stack_stack.push_back(indent_stack);
}
-void GDScriptTokenizer::pop_expression_indented_block() {
- ERR_FAIL_COND(indent_stack_stack.size() == 0);
+void GDScriptTokenizerText::pop_expression_indented_block() {
+ ERR_FAIL_COND(indent_stack_stack.is_empty());
indent_stack = indent_stack_stack.back()->get();
indent_stack_stack.pop_back();
}
-int GDScriptTokenizer::get_cursor_line() const {
+int GDScriptTokenizerText::get_cursor_line() const {
return cursor_line;
}
-int GDScriptTokenizer::get_cursor_column() const {
+int GDScriptTokenizerText::get_cursor_column() const {
return cursor_column;
}
-bool GDScriptTokenizer::is_past_cursor() const {
+bool GDScriptTokenizerText::is_past_cursor() const {
if (line < cursor_line) {
return false;
}
@@ -310,7 +310,7 @@ bool GDScriptTokenizer::is_past_cursor() const {
return true;
}
-char32_t GDScriptTokenizer::_advance() {
+char32_t GDScriptTokenizerText::_advance() {
if (unlikely(_is_at_end())) {
return '\0';
}
@@ -329,11 +329,11 @@ char32_t GDScriptTokenizer::_advance() {
return _peek(-1);
}
-void GDScriptTokenizer::push_paren(char32_t p_char) {
+void GDScriptTokenizerText::push_paren(char32_t p_char) {
paren_stack.push_back(p_char);
}
-bool GDScriptTokenizer::pop_paren(char32_t p_expected) {
+bool GDScriptTokenizerText::pop_paren(char32_t p_expected) {
if (paren_stack.is_empty()) {
return false;
}
@@ -343,13 +343,13 @@ bool GDScriptTokenizer::pop_paren(char32_t p_expected) {
return actual == p_expected;
}
-GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
+GDScriptTokenizer::Token GDScriptTokenizerText::pop_error() {
Token error = error_stack.back()->get();
error_stack.pop_back();
return error;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
Token token(p_type);
token.start_line = start_line;
token.end_line = line;
@@ -408,35 +408,35 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
return token;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_literal(const Variant &p_literal) {
Token token = make_token(Token::LITERAL);
token.literal = p_literal;
return token;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_identifier(const StringName &p_identifier) {
Token identifier = make_token(Token::IDENTIFIER);
identifier.literal = p_identifier;
return identifier;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_error(const String &p_message) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_error(const String &p_message) {
Token error = make_token(Token::ERROR);
error.literal = p_message;
return error;
}
-void GDScriptTokenizer::push_error(const String &p_message) {
+void GDScriptTokenizerText::push_error(const String &p_message) {
Token error = make_error(p_message);
error_stack.push_back(error);
}
-void GDScriptTokenizer::push_error(const Token &p_error) {
+void GDScriptTokenizerText::push_error(const Token &p_error) {
error_stack.push_back(p_error);
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_paren_error(char32_t p_paren) {
if (paren_stack.is_empty()) {
return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
}
@@ -445,7 +445,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) {
return error;
}
-GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
+GDScriptTokenizer::Token GDScriptTokenizerText::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
const char32_t *next = _current + 1;
int chars = 2; // Two already matched.
@@ -469,7 +469,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
+GDScriptTokenizer::Token GDScriptTokenizerText::annotation() {
if (is_unicode_identifier_start(_peek())) {
_advance(); // Consume start character.
} else {
@@ -550,7 +550,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
#define MAX_KEYWORD_LENGTH 10
#ifdef DEBUG_ENABLED
-void GDScriptTokenizer::make_keyword_list() {
+void GDScriptTokenizerText::make_keyword_list() {
#define KEYWORD_LINE(keyword, token_type) keyword,
#define KEYWORD_GROUP_IGNORE(group)
keyword_list = {
@@ -561,7 +561,7 @@ void GDScriptTokenizer::make_keyword_list() {
}
#endif // DEBUG_ENABLED
-GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() {
bool only_ascii = _peek(-1) < 128;
// Consume all identifier characters.
@@ -611,7 +611,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
if (keyword_length == len && name == keyword) { \
- return make_token(token_type); \
+ Token kw = make_token(token_type); \
+ kw.literal = name; \
+ return kw; \
} \
}
@@ -646,7 +648,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#undef MIN_KEYWORD_LENGTH
#undef KEYWORDS
-void GDScriptTokenizer::newline(bool p_make_token) {
+void GDScriptTokenizerText::newline(bool p_make_token) {
// Don't overwrite previous newline, nor create if we want a line continuation.
if (p_make_token && !pending_newline && !line_continuation) {
Token newline(Token::NEWLINE);
@@ -667,11 +669,12 @@ void GDScriptTokenizer::newline(bool p_make_token) {
leftmost_column = 1;
}
-GDScriptTokenizer::Token GDScriptTokenizer::number() {
+GDScriptTokenizer::Token GDScriptTokenizerText::number() {
int base = 10;
bool has_decimal = false;
bool has_exponent = false;
bool has_error = false;
+ bool need_digits = false;
bool (*digit_check_func)(char32_t) = is_digit;
// Sign before hexadecimal or binary.
@@ -686,11 +689,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
// Hexadecimal.
base = 16;
digit_check_func = is_hex_digit;
+ need_digits = true;
_advance();
} else if (_peek() == 'b') {
// Binary.
base = 2;
digit_check_func = is_binary_digit;
+ need_digits = true;
_advance();
}
}
@@ -717,6 +722,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
previous_was_underscore = true;
} else {
+ need_digits = false;
previous_was_underscore = false;
}
_advance();
@@ -820,6 +826,16 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
}
+ if (need_digits) {
+ // No digits in hex or bin literal.
+ Token error = make_error(vformat(R"(Expected %s digit after "0%c".)", (base == 16 ? "hexadecimal" : "binary"), (base == 16 ? 'x' : 'b')));
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ return error;
+ }
+
// Detect extra decimal point.
if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') {
Token error = make_error("Cannot use a decimal point twice in a number.");
@@ -854,7 +870,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::string() {
+GDScriptTokenizer::Token GDScriptTokenizerText::string() {
enum StringType {
STRING_REGULAR,
STRING_NAME,
@@ -1140,7 +1156,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_literal(string);
}
-void GDScriptTokenizer::check_indent() {
+void GDScriptTokenizerText::check_indent() {
ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line.");
if (_is_at_end()) {
@@ -1309,13 +1325,13 @@ void GDScriptTokenizer::check_indent() {
}
}
-String GDScriptTokenizer::_get_indent_char_name(char32_t ch) {
+String GDScriptTokenizerText::_get_indent_char_name(char32_t ch) {
ERR_FAIL_COND_V(ch != ' ' && ch != '\t', String(&ch, 1).c_escape());
return ch == ' ' ? "space" : "tab";
}
-void GDScriptTokenizer::_skip_whitespace() {
+void GDScriptTokenizerText::_skip_whitespace() {
if (pending_indents != 0) {
// Still have some indent/dedent tokens to give.
return;
@@ -1377,7 +1393,7 @@ void GDScriptTokenizer::_skip_whitespace() {
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::scan() {
+GDScriptTokenizer::Token GDScriptTokenizerText::scan() {
if (has_error()) {
return pop_error();
}
@@ -1439,6 +1455,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (_peek() != '\n') {
return make_error("Expected new line after \"\\\".");
}
+ continuation_lines.push_back(line);
_advance();
newline(false);
line_continuation = true;
@@ -1659,7 +1676,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
}
}
-GDScriptTokenizer::GDScriptTokenizer() {
+GDScriptTokenizerText::GDScriptTokenizerText() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index a64aaf6820..5d76375173 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -181,14 +181,13 @@ public:
bool can_precede_bin_op() const;
bool is_identifier() const;
bool is_node_name() const;
- StringName get_identifier() const { return source; }
+ StringName get_identifier() const { return literal; }
Token(Type p_type) {
type = p_type;
}
- Token() {
- }
+ Token() {}
};
#ifdef TOOLS_ENABLED
@@ -203,12 +202,26 @@ public:
new_line = p_new_line;
}
};
- const HashMap<int, CommentData> &get_comments() const {
- return comments;
- }
+ virtual const HashMap<int, CommentData> &get_comments() const = 0;
#endif // TOOLS_ENABLED
-private:
+ static String get_token_name(Token::Type p_token_type);
+
+ virtual int get_cursor_line() const = 0;
+ virtual int get_cursor_column() const = 0;
+ virtual void set_cursor_position(int p_line, int p_column) = 0;
+ virtual void set_multiline_mode(bool p_state) = 0;
+ virtual bool is_past_cursor() const = 0;
+ virtual void push_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() = 0;
+
+ virtual Token scan() = 0;
+
+ virtual ~GDScriptTokenizer() {}
+};
+
+class GDScriptTokenizerText : public GDScriptTokenizer {
String source;
const char32_t *_source = nullptr;
const char32_t *_current = nullptr;
@@ -235,6 +248,7 @@ private:
char32_t indent_char = '\0';
int position = 0;
int length = 0;
+ Vector<int> continuation_lines;
#ifdef DEBUG_ENABLED
Vector<String> keyword_list;
#endif // DEBUG_ENABLED
@@ -275,20 +289,28 @@ private:
Token annotation();
public:
- Token scan();
-
void set_source_code(const String &p_source_code);
- int get_cursor_line() const;
- int get_cursor_column() const;
- void set_cursor_position(int p_line, int p_column);
- void set_multiline_mode(bool p_state);
- bool is_past_cursor() const;
- static String get_token_name(Token::Type p_token_type);
- void push_expression_indented_block(); // For lambdas, or blocks inside expressions.
- void pop_expression_indented_block(); // For lambdas, or blocks inside expressions.
+ const Vector<int> &get_continuation_lines() const { return continuation_lines; }
+
+ virtual int get_cursor_line() const override;
+ virtual int get_cursor_column() const override;
+ virtual void set_cursor_position(int p_line, int p_column) override;
+ virtual void set_multiline_mode(bool p_state) override;
+ virtual bool is_past_cursor() const override;
+ virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() override { return true; }
+
+#ifdef TOOLS_ENABLED
+ virtual const HashMap<int, CommentData> &get_comments() const override {
+ return comments;
+ }
+#endif // TOOLS_ENABLED
+
+ virtual Token scan() override;
- GDScriptTokenizer();
+ GDScriptTokenizerText();
};
#endif // GDSCRIPT_TOKENIZER_H
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
new file mode 100644
index 0000000000..db523ea941
--- /dev/null
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -0,0 +1,493 @@
+/**************************************************************************/
+/* gdscript_tokenizer_buffer.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gdscript_tokenizer_buffer.h"
+
+#include "core/io/compression.h"
+#include "core/io/marshalls.h"
+
+#define TOKENIZER_VERSION 100
+
+int GDScriptTokenizerBuffer::_token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map) {
+ int pos = p_start;
+
+ int token_type = p_token.type & TOKEN_MASK;
+
+ switch (p_token.type) {
+ case GDScriptTokenizer::Token::ANNOTATION:
+ case GDScriptTokenizer::Token::IDENTIFIER: {
+ // Add identifier to map.
+ int identifier_pos;
+ StringName id = p_token.get_identifier();
+ if (r_identifiers_map.has(id)) {
+ identifier_pos = r_identifiers_map[id];
+ } else {
+ identifier_pos = r_identifiers_map.size();
+ r_identifiers_map[id] = identifier_pos;
+ }
+ token_type |= identifier_pos << TOKEN_BITS;
+ } break;
+ case GDScriptTokenizer::Token::ERROR:
+ case GDScriptTokenizer::Token::LITERAL: {
+ // Add literal to map.
+ int constant_pos;
+ if (r_constants_map.has(p_token.literal)) {
+ constant_pos = r_constants_map[p_token.literal];
+ } else {
+ constant_pos = r_constants_map.size();
+ r_constants_map[p_token.literal] = constant_pos;
+ }
+ token_type |= constant_pos << TOKEN_BITS;
+ } break;
+ default:
+ break;
+ }
+
+ // Encode token.
+ int token_len;
+ if (token_type & TOKEN_MASK) {
+ token_len = 8;
+ r_buffer.resize(pos + token_len);
+ encode_uint32(token_type | TOKEN_BYTE_MASK, &r_buffer.write[pos]);
+ pos += 4;
+ } else {
+ token_len = 5;
+ r_buffer.resize(pos + token_len);
+ r_buffer.write[pos] = token_type;
+ pos++;
+ }
+ encode_uint32(p_token.start_line, &r_buffer.write[pos]);
+ return token_len;
+}
+
+GDScriptTokenizer::Token GDScriptTokenizerBuffer::_binary_to_token(const uint8_t *p_buffer) {
+ Token token;
+ const uint8_t *b = p_buffer;
+
+ uint32_t token_type = decode_uint32(b);
+ token.type = (Token::Type)(token_type & TOKEN_MASK);
+ if (token_type & TOKEN_BYTE_MASK) {
+ b += 4;
+ } else {
+ b++;
+ }
+ token.start_line = decode_uint32(b);
+ token.end_line = token.start_line;
+
+ token.literal = token.get_name();
+ if (token.type == Token::CONST_NAN) {
+ token.literal = String("NAN"); // Special case since name and notation are different.
+ }
+
+ switch (token.type) {
+ case GDScriptTokenizer::Token::ANNOTATION:
+ case GDScriptTokenizer::Token::IDENTIFIER: {
+ // Get name from map.
+ int identifier_pos = token_type >> TOKEN_BITS;
+ if (unlikely(identifier_pos >= identifiers.size())) {
+ Token error;
+ error.type = Token::ERROR;
+ error.literal = "Identifier index out of bounds.";
+ return error;
+ }
+ token.literal = identifiers[identifier_pos];
+ } break;
+ case GDScriptTokenizer::Token::ERROR:
+ case GDScriptTokenizer::Token::LITERAL: {
+ // Get literal from map.
+ int constant_pos = token_type >> TOKEN_BITS;
+ if (unlikely(constant_pos >= constants.size())) {
+ Token error;
+ error.type = Token::ERROR;
+ error.literal = "Constant index out of bounds.";
+ return error;
+ }
+ token.literal = constants[constant_pos];
+ } break;
+ default:
+ break;
+ }
+
+ return token;
+}
+
+Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
+ const uint8_t *buf = p_buffer.ptr();
+ ERR_FAIL_COND_V(p_buffer.size() < 12 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
+
+ int version = decode_uint32(&buf[4]);
+ ERR_FAIL_COND_V_MSG(version > TOKENIZER_VERSION, ERR_INVALID_DATA, "Binary GDScript is too recent! Please use a newer engine version.");
+
+ int decompressed_size = decode_uint32(&buf[8]);
+
+ Vector<uint8_t> contents;
+ if (decompressed_size == 0) {
+ contents = p_buffer.slice(12);
+ } else {
+ contents.resize(decompressed_size);
+ int result = Compression::decompress(contents.ptrw(), contents.size(), &buf[12], p_buffer.size() - 12, Compression::MODE_ZSTD);
+ ERR_FAIL_COND_V_MSG(result != decompressed_size, ERR_INVALID_DATA, "Error decompressing GDScript tokenizer buffer.");
+ }
+
+ int total_len = contents.size();
+ buf = contents.ptr();
+ uint32_t identifier_count = decode_uint32(&buf[0]);
+ uint32_t constant_count = decode_uint32(&buf[4]);
+ uint32_t token_line_count = decode_uint32(&buf[8]);
+ uint32_t token_count = decode_uint32(&buf[16]);
+
+ const uint8_t *b = &buf[20];
+ total_len -= 20;
+
+ identifiers.resize(identifier_count);
+ for (uint32_t i = 0; i < identifier_count; i++) {
+ uint32_t len = decode_uint32(b);
+ total_len -= 4;
+ ERR_FAIL_COND_V((len * 4u) > (uint32_t)total_len, ERR_INVALID_DATA);
+ b += 4;
+ Vector<uint32_t> cs;
+ cs.resize(len);
+ for (uint32_t j = 0; j < len; j++) {
+ uint8_t tmp[4];
+ for (uint32_t k = 0; k < 4; k++) {
+ tmp[k] = b[j * 4 + k] ^ 0xb6;
+ }
+ cs.write[j] = decode_uint32(tmp);
+ }
+
+ String s(reinterpret_cast<const char32_t *>(cs.ptr()), len);
+ b += len * 4;
+ total_len -= len * 4;
+ identifiers.write[i] = s;
+ }
+
+ constants.resize(constant_count);
+ for (uint32_t i = 0; i < constant_count; i++) {
+ Variant v;
+ int len;
+ Error err = decode_variant(v, b, total_len, &len, false);
+ if (err) {
+ return err;
+ }
+ b += len;
+ total_len -= len;
+ constants.write[i] = v;
+ }
+
+ for (uint32_t i = 0; i < token_line_count; i++) {
+ ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
+ uint32_t token_index = decode_uint32(b);
+ b += 4;
+ uint32_t line = decode_uint32(b);
+ b += 4;
+ total_len -= 8;
+ token_lines[token_index] = line;
+ }
+ for (uint32_t i = 0; i < token_line_count; i++) {
+ ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
+ uint32_t token_index = decode_uint32(b);
+ b += 4;
+ uint32_t column = decode_uint32(b);
+ b += 4;
+ total_len -= 8;
+ token_columns[token_index] = column;
+ }
+
+ tokens.resize(token_count);
+ for (uint32_t i = 0; i < token_count; i++) {
+ int token_len = 5;
+ if ((*b) & TOKEN_BYTE_MASK) {
+ token_len = 8;
+ }
+ ERR_FAIL_COND_V(total_len < token_len, ERR_INVALID_DATA);
+ Token token = _binary_to_token(b);
+ b += token_len;
+ ERR_FAIL_INDEX_V(token.type, Token::TK_MAX, ERR_INVALID_DATA);
+ tokens.write[i] = token;
+ total_len -= token_len;
+ }
+
+ ERR_FAIL_COND_V(total_len > 0, ERR_INVALID_DATA);
+
+ return OK;
+}
+
+Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code, CompressMode p_compress_mode) {
+ HashMap<StringName, uint32_t> identifier_map;
+ HashMap<Variant, uint32_t, VariantHasher, VariantComparator> constant_map;
+ Vector<uint8_t> token_buffer;
+ HashMap<uint32_t, uint32_t> token_lines;
+ HashMap<uint32_t, uint32_t> token_columns;
+
+ GDScriptTokenizerText tokenizer;
+ tokenizer.set_source_code(p_code);
+ tokenizer.set_multiline_mode(true); // Ignore whitespace tokens.
+ Token current = tokenizer.scan();
+ int token_pos = 0;
+ int last_token_line = 0;
+ int token_counter = 0;
+
+ while (current.type != Token::TK_EOF) {
+ int token_len = _token_to_binary(current, token_buffer, token_pos, identifier_map, constant_map);
+ token_pos += token_len;
+ if (token_counter > 0 && current.start_line > last_token_line) {
+ token_lines[token_counter] = current.start_line;
+ token_columns[token_counter] = current.start_column;
+ }
+ last_token_line = current.end_line;
+
+ current = tokenizer.scan();
+ token_counter++;
+ }
+
+ // Reverse maps.
+ Vector<StringName> rev_identifier_map;
+ rev_identifier_map.resize(identifier_map.size());
+ for (const KeyValue<StringName, uint32_t> &E : identifier_map) {
+ rev_identifier_map.write[E.value] = E.key;
+ }
+ Vector<Variant> rev_constant_map;
+ rev_constant_map.resize(constant_map.size());
+ for (const KeyValue<Variant, uint32_t> &E : constant_map) {
+ rev_constant_map.write[E.value] = E.key;
+ }
+ HashMap<uint32_t, uint32_t> rev_token_lines;
+ for (const KeyValue<uint32_t, uint32_t> &E : token_lines) {
+ rev_token_lines[E.value] = E.key;
+ }
+
+ // Remove continuation lines from map.
+ for (int line : tokenizer.get_continuation_lines()) {
+ if (rev_token_lines.has(line + 1)) {
+ token_lines.erase(rev_token_lines[line + 1]);
+ token_columns.erase(rev_token_lines[line + 1]);
+ }
+ }
+
+ Vector<uint8_t> contents;
+ contents.resize(20);
+ encode_uint32(identifier_map.size(), &contents.write[0]);
+ encode_uint32(constant_map.size(), &contents.write[4]);
+ encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(token_counter, &contents.write[16]);
+
+ int buf_pos = 20;
+
+ // Save identifiers.
+ for (const StringName &id : rev_identifier_map) {
+ String s = id.operator String();
+ int len = s.length();
+
+ contents.resize(buf_pos + (len + 1) * 4);
+
+ encode_uint32(len, &contents.write[buf_pos]);
+ buf_pos += 4;
+
+ for (int i = 0; i < len; i++) {
+ uint8_t tmp[4];
+ encode_uint32(s[i], tmp);
+
+ for (int b = 0; b < 4; b++) {
+ contents.write[buf_pos + b] = tmp[b] ^ 0xb6;
+ }
+
+ buf_pos += 4;
+ }
+ }
+
+ // Save constants.
+ for (const Variant &v : rev_constant_map) {
+ int len;
+ // Objects cannot be constant, never encode objects.
+ Error err = encode_variant(v, nullptr, len, false);
+ ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
+ contents.resize(buf_pos + len);
+ encode_variant(v, &contents.write[buf_pos], len, false);
+ buf_pos += len;
+ }
+
+ // Save lines and columns.
+ contents.resize(buf_pos + token_lines.size() * 16);
+ for (const KeyValue<uint32_t, uint32_t> &e : token_lines) {
+ encode_uint32(e.key, &contents.write[buf_pos]);
+ buf_pos += 4;
+ encode_uint32(e.value, &contents.write[buf_pos]);
+ buf_pos += 4;
+ }
+ for (const KeyValue<uint32_t, uint32_t> &e : token_columns) {
+ encode_uint32(e.key, &contents.write[buf_pos]);
+ buf_pos += 4;
+ encode_uint32(e.value, &contents.write[buf_pos]);
+ buf_pos += 4;
+ }
+
+ // Store tokens.
+ contents.append_array(token_buffer);
+
+ Vector<uint8_t> buf;
+
+ // Save header.
+ buf.resize(12);
+ buf.write[0] = 'G';
+ buf.write[1] = 'D';
+ buf.write[2] = 'S';
+ buf.write[3] = 'C';
+ encode_uint32(TOKENIZER_VERSION, &buf.write[4]);
+
+ switch (p_compress_mode) {
+ case COMPRESS_NONE:
+ encode_uint32(0u, &buf.write[8]);
+ buf.append_array(contents);
+ break;
+
+ case COMPRESS_ZSTD: {
+ encode_uint32(contents.size(), &buf.write[8]);
+ Vector<uint8_t> compressed;
+ int max_size = Compression::get_max_compressed_buffer_size(contents.size(), Compression::MODE_ZSTD);
+ compressed.resize(max_size);
+
+ int compressed_size = Compression::compress(compressed.ptrw(), contents.ptr(), contents.size(), Compression::MODE_ZSTD);
+ ERR_FAIL_COND_V_MSG(compressed_size < 0, Vector<uint8_t>(), "Error compressing GDScript tokenizer buffer.");
+ compressed.resize(compressed_size);
+
+ buf.append_array(compressed);
+ } break;
+ }
+
+ return buf;
+}
+
+int GDScriptTokenizerBuffer::get_cursor_line() const {
+ return 0;
+}
+
+int GDScriptTokenizerBuffer::get_cursor_column() const {
+ return 0;
+}
+
+void GDScriptTokenizerBuffer::set_cursor_position(int p_line, int p_column) {
+}
+
+void GDScriptTokenizerBuffer::set_multiline_mode(bool p_state) {
+ multiline_mode = p_state;
+}
+
+bool GDScriptTokenizerBuffer::is_past_cursor() const {
+ return false;
+}
+
+void GDScriptTokenizerBuffer::push_expression_indented_block() {
+ indent_stack_stack.push_back(indent_stack);
+}
+
+void GDScriptTokenizerBuffer::pop_expression_indented_block() {
+ ERR_FAIL_COND(indent_stack_stack.is_empty());
+ indent_stack = indent_stack_stack.back()->get();
+ indent_stack_stack.pop_back();
+}
+
+GDScriptTokenizer::Token GDScriptTokenizerBuffer::scan() {
+ // Add final newline.
+ if (current >= tokens.size() && !last_token_was_newline) {
+ Token newline;
+ newline.type = Token::NEWLINE;
+ newline.start_line = current_line;
+ newline.end_line = current_line;
+ last_token_was_newline = true;
+ return newline;
+ }
+
+ // Resolve pending indentation change.
+ if (pending_indents > 0) {
+ pending_indents--;
+ Token indent;
+ indent.type = Token::INDENT;
+ indent.start_line = current_line;
+ indent.end_line = current_line;
+ return indent;
+ } else if (pending_indents < 0) {
+ pending_indents++;
+ Token dedent;
+ dedent.type = Token::DEDENT;
+ dedent.start_line = current_line;
+ dedent.end_line = current_line;
+ return dedent;
+ }
+
+ if (current >= tokens.size()) {
+ if (!indent_stack.is_empty()) {
+ pending_indents -= indent_stack.size();
+ indent_stack.clear();
+ return scan();
+ }
+ Token eof;
+ eof.type = Token::TK_EOF;
+ return eof;
+ };
+
+ if (!last_token_was_newline && token_lines.has(current)) {
+ current_line = token_lines[current];
+ uint32_t current_column = token_columns[current];
+
+ // Check if there's a need to indent/dedent.
+ if (!multiline_mode) {
+ uint32_t previous_indent = 0;
+ if (!indent_stack.is_empty()) {
+ previous_indent = indent_stack.back()->get();
+ }
+ if (current_column - 1 > previous_indent) {
+ pending_indents++;
+ indent_stack.push_back(current_column - 1);
+ } else {
+ while (current_column - 1 < previous_indent) {
+ pending_indents--;
+ indent_stack.pop_back();
+ if (indent_stack.is_empty()) {
+ break;
+ }
+ previous_indent = indent_stack.back()->get();
+ }
+ }
+
+ Token newline;
+ newline.type = Token::NEWLINE;
+ newline.start_line = current_line;
+ newline.end_line = current_line;
+ last_token_was_newline = true;
+
+ return newline;
+ }
+ }
+
+ last_token_was_newline = false;
+
+ Token token = tokens[current++];
+ return token;
+}
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.h b/modules/gdscript/gdscript_tokenizer_buffer.h
new file mode 100644
index 0000000000..55df66e50f
--- /dev/null
+++ b/modules/gdscript/gdscript_tokenizer_buffer.h
@@ -0,0 +1,93 @@
+/**************************************************************************/
+/* gdscript_tokenizer_buffer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GDSCRIPT_TOKENIZER_BUFFER_H
+#define GDSCRIPT_TOKENIZER_BUFFER_H
+
+#include "gdscript_tokenizer.h"
+
+class GDScriptTokenizerBuffer : public GDScriptTokenizer {
+public:
+ enum CompressMode {
+ COMPRESS_NONE,
+ COMPRESS_ZSTD,
+ };
+
+ enum {
+ TOKEN_BYTE_MASK = 0x80,
+ TOKEN_BITS = 8,
+ TOKEN_MASK = (1 << (TOKEN_BITS - 1)) - 1,
+ };
+
+ Vector<StringName> identifiers;
+ Vector<Variant> constants;
+ Vector<int> continuation_lines;
+ HashMap<int, int> token_lines;
+ HashMap<int, int> token_columns;
+ Vector<Token> tokens;
+ int current = 0;
+ uint32_t current_line = 1;
+
+ bool multiline_mode = false;
+ List<int> indent_stack;
+ List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point.
+ int pending_indents = 0;
+ bool last_token_was_newline = false;
+
+#ifdef TOOLS_ENABLED
+ HashMap<int, CommentData> dummy;
+#endif // TOOLS_ENABLED
+
+ static int _token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map);
+ Token _binary_to_token(const uint8_t *p_buffer);
+
+public:
+ Error set_code_buffer(const Vector<uint8_t> &p_buffer);
+ static Vector<uint8_t> parse_code_string(const String &p_code, CompressMode p_compress_mode);
+
+ virtual int get_cursor_line() const override;
+ virtual int get_cursor_column() const override;
+ virtual void set_cursor_position(int p_line, int p_column) override;
+ virtual void set_multiline_mode(bool p_state) override;
+ virtual bool is_past_cursor() const override;
+ virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() override { return false; };
+
+#ifdef TOOLS_ENABLED
+ virtual const HashMap<int, CommentData> &get_comments() const override {
+ return dummy;
+ }
+#endif // TOOLS_ENABLED
+
+ virtual Token scan() override;
+};
+
+#endif // GDSCRIPT_TOKENIZER_BUFFER_H
diff --git a/modules/gdscript/gdscript_utility_callable.cpp b/modules/gdscript/gdscript_utility_callable.cpp
new file mode 100644
index 0000000000..7708a18044
--- /dev/null
+++ b/modules/gdscript/gdscript_utility_callable.cpp
@@ -0,0 +1,111 @@
+/**************************************************************************/
+/* gdscript_utility_callable.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gdscript_utility_callable.h"
+
+#include "core/templates/hashfuncs.h"
+
+bool GDScriptUtilityCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ return p_a->hash() == p_b->hash();
+}
+
+bool GDScriptUtilityCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ return p_a->hash() < p_b->hash();
+}
+
+uint32_t GDScriptUtilityCallable::hash() const {
+ return h;
+}
+
+String GDScriptUtilityCallable::get_as_text() const {
+ String scope;
+ switch (type) {
+ case TYPE_INVALID:
+ scope = "<invalid scope>";
+ break;
+ case TYPE_GLOBAL:
+ scope = "@GlobalScope";
+ break;
+ case TYPE_GDSCRIPT:
+ scope = "@GDScript";
+ break;
+ }
+ return vformat("%s::%s (Callable)", scope, function_name);
+}
+
+CallableCustom::CompareEqualFunc GDScriptUtilityCallable::get_compare_equal_func() const {
+ return compare_equal;
+}
+
+CallableCustom::CompareLessFunc GDScriptUtilityCallable::get_compare_less_func() const {
+ return compare_less;
+}
+
+bool GDScriptUtilityCallable::is_valid() const {
+ return type != TYPE_INVALID;
+}
+
+StringName GDScriptUtilityCallable::get_method() const {
+ return function_name;
+}
+
+ObjectID GDScriptUtilityCallable::get_object() const {
+ return ObjectID();
+}
+
+void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ switch (type) {
+ case TYPE_INVALID:
+ r_return_value = vformat(R"(Trying to call invalid utility function "%s".)", function_name);
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ break;
+ case TYPE_GLOBAL:
+ Variant::call_utility_function(function_name, &r_return_value, p_arguments, p_argcount, r_call_error);
+ break;
+ case TYPE_GDSCRIPT:
+ gdscript_function(&r_return_value, p_arguments, p_argcount, r_call_error);
+ break;
+ }
+}
+
+GDScriptUtilityCallable::GDScriptUtilityCallable(const StringName &p_function_name) {
+ function_name = p_function_name;
+ if (GDScriptUtilityFunctions::function_exists(p_function_name)) {
+ type = TYPE_GDSCRIPT;
+ gdscript_function = GDScriptUtilityFunctions::get_function(p_function_name);
+ } else if (Variant::has_utility_function(p_function_name)) {
+ type = TYPE_GLOBAL;
+ } else {
+ ERR_PRINT(vformat(R"(Unknown utility function "%s".)", p_function_name));
+ }
+ h = p_function_name.hash();
+}
diff --git a/modules/gdscript/gdscript_utility_callable.h b/modules/gdscript/gdscript_utility_callable.h
new file mode 100644
index 0000000000..675bc4ddd9
--- /dev/null
+++ b/modules/gdscript/gdscript_utility_callable.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* gdscript_utility_callable.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GDSCRIPT_UTILITY_CALLABLE_H
+#define GDSCRIPT_UTILITY_CALLABLE_H
+
+#include "gdscript_utility_functions.h"
+
+#include "core/variant/callable.h"
+
+class GDScriptUtilityCallable : public CallableCustom {
+ StringName function_name;
+ enum Type {
+ TYPE_INVALID,
+ TYPE_GLOBAL,
+ TYPE_GDSCRIPT,
+ };
+ Type type = TYPE_INVALID;
+ GDScriptUtilityFunctions::FunctionPtr gdscript_function = nullptr;
+ uint32_t h = 0;
+
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+public:
+ uint32_t hash() const override;
+ String get_as_text() const override;
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
+ bool is_valid() const override;
+ StringName get_method() const override;
+ ObjectID get_object() const override;
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+ GDScriptUtilityCallable(const StringName &p_function_name);
+};
+
+#endif // GDSCRIPT_UTILITY_CALLABLE_H
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 40c564c36b..f8cb460e40 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -194,9 +194,9 @@ struct GDScriptUtilityFunctionsDefinitions {
// Calculate how many.
int count = 0;
if (incr > 0) {
- count = ((to - from - 1) / incr) + 1;
+ count = Math::division_round_up(to - from, incr);
} else {
- count = ((from - to - 1) / -incr) + 1;
+ count = Math::division_round_up(from - to, -incr);
}
Error err = arr.resize(count);
@@ -470,7 +470,8 @@ struct GDScriptUtilityFunctionsDefinitions {
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
switch (p_args[0]->get_type()) {
- case Variant::STRING: {
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
String d = *p_args[0];
*r_ret = d.length();
} break;
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 3abfc7f8e3..1a8c22cc11 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -655,7 +655,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
bool exit_ok = false;
bool awaited = false;
- int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
+ int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? (int)p_instance->members.size() : 0 };
#endif
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
@@ -873,8 +873,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(value, 2);
bool valid;
+#ifdef DEBUG_ENABLED
+ Variant::VariantSetError err_code;
+ dst->set(*index, *value, &valid, &err_code);
+#else
dst->set(*index, *value, &valid);
-
+#endif
#ifdef DEBUG_ENABLED
if (!valid) {
Object *obj = dst->get_validated_object();
@@ -891,7 +895,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
} else {
v = "of type '" + _get_var_type(index) + "'";
}
- err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
+ err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
+ if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) {
+ err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+ }
}
OPCODE_BREAK;
}
@@ -922,7 +929,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
} else {
v = "of type '" + _get_var_type(index) + "'";
}
- err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
+ err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
OPCODE_BREAK;
}
#endif
@@ -972,7 +979,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool valid;
#ifdef DEBUG_ENABLED
// Allow better error message in cases where src and dst are the same stack position.
- Variant ret = src->get(*index, &valid);
+ Variant::VariantGetError err_code;
+ Variant ret = src->get(*index, &valid, &err_code);
#else
*dst = src->get(*index, &valid);
@@ -985,7 +993,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
} else {
v = "of type '" + _get_var_type(index) + "'";
}
- err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
+ err_text = "Invalid access to property or key " + v + " on a base object of type '" + _get_var_type(src) + "'.";
+ if (err_code == Variant::VariantGetError::GET_INDEXED_ERR) {
+ err_text = "Invalid access of index " + v + " on a base object of type: '" + _get_var_type(src) + "'.";
+ }
OPCODE_BREAK;
}
*dst = ret;
@@ -1021,7 +1032,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
} else {
v = "of type '" + _get_var_type(key) + "'";
}
- err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
+ err_text = "Invalid access to property or key " + v + " on a base object of type '" + _get_var_type(src) + "'.";
OPCODE_BREAK;
}
*dst = ret;
@@ -1086,7 +1097,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (read_only_property) {
err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst));
} else {
- err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+ err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
}
OPCODE_BREAK;
}
@@ -1131,7 +1142,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "').";
+ err_text = "Invalid access to property or key '" + index->operator String() + "' on a base object of type '" + _get_var_type(src) + "'.";
OPCODE_BREAK;
}
*dst = ret;
@@ -2473,7 +2484,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant::construct(ret_type, retvalue, const_cast<const Variant **>(&r), 1, ce);
} else {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
Variant::get_type_name(r->get_type()), Variant::get_type_name(ret_type));
#endif // DEBUG_ENABLED
@@ -2503,9 +2514,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (r->get_type() != Variant::ARRAY) {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Array[%s]".)",
- _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type));
-#endif // DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "Array[%s]".)",
+ Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type));
+#endif
OPCODE_BREAK;
}
@@ -2536,7 +2547,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(!nc);
if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) {
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
Variant::get_type_name(r->get_type()), nc->get_name());
OPCODE_BREAK;
}
@@ -2554,7 +2565,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif // DEBUG_ENABLED
if (ret_obj && !ClassDB::is_parent_class(ret_obj->get_class_name(), nc->get_name())) {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
ret_obj->get_class_name(), nc->get_name());
#endif // DEBUG_ENABLED
OPCODE_BREAK;
@@ -2577,7 +2588,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
Variant::get_type_name(r->get_type()), GDScript::debug_get_script_name(Ref<Script>(base_type)));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
@@ -2599,7 +2610,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
ScriptInstance *ret_inst = ret_obj->get_script_instance();
if (!ret_inst) {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
ret_obj->get_class_name(), GDScript::debug_get_script_name(Ref<GDScript>(base_type)));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
@@ -2618,7 +2629,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (!valid) {
#ifdef DEBUG_ENABLED
- err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
+ err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)",
GDScript::debug_get_script_name(ret_obj->get_script_instance()->get_script()), GDScript::debug_get_script_name(Ref<GDScript>(base_type)));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 36806d2f73..ad7af34bf1 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -48,17 +48,17 @@ lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {
lsp::Position res;
// Special case: `line = 0` -> root class (range covers everything).
- if (this->line <= 0) {
+ if (line <= 0) {
return res;
}
// Special case: `line = p_lines.size() + 1` -> root class (range covers everything).
- if (this->line >= p_lines.size() + 1) {
+ if (line >= p_lines.size() + 1) {
res.line = p_lines.size();
return res;
}
- res.line = this->line - 1;
+ res.line = line - 1;
// Note: character outside of `pos_line.length()-1` is valid.
- res.character = this->column - 1;
+ res.character = column - 1;
String pos_line = p_lines[res.line];
if (pos_line.contains("\t")) {
@@ -67,7 +67,7 @@ lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {
int in_col = 1;
int res_char = 0;
- while (res_char < pos_line.size() && in_col < this->column) {
+ while (res_char < pos_line.size() && in_col < column) {
if (pos_line[res_char] == '\t') {
in_col += tab_size;
res_char++;
@@ -191,7 +191,7 @@ void ExtendGDScriptParser::update_symbols() {
void ExtendGDScriptParser::update_document_links(const String &p_code) {
document_links.clear();
- GDScriptTokenizer scr_tokenizer;
+ GDScriptTokenizerText scr_tokenizer;
Ref<FileAccess> fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
scr_tokenizer.set_source_code(p_code);
while (true) {
@@ -211,7 +211,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
String value = const_val;
lsp::DocumentLink link;
link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path);
- link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(this->lines);
+ link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(lines);
document_links.push_back(link);
}
}
@@ -222,7 +222,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
lsp::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const {
GodotPosition start(p_node->start_line, p_node->start_column);
GodotPosition end(p_node->end_line, p_node->end_column);
- return GodotRange(start, end).to_lsp(this->lines);
+ return GodotRange(start, end).to_lsp(lines);
}
void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
@@ -394,8 +394,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.name = m.enum_value.identifier->name;
symbol.kind = lsp::SymbolKind::EnumMember;
symbol.deprecated = false;
- symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.leftmost_column).to_lsp(this->lines);
- symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.rightmost_column).to_lsp(this->lines);
+ symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.leftmost_column).to_lsp(lines);
+ symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.rightmost_column).to_lsp(lines);
symbol.selectionRange = range_of_node(m.enum_value.identifier);
symbol.documentation = m.enum_value.doc_data.description;
symbol.uri = uri;
@@ -430,8 +430,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
child.name = value.identifier->name;
child.kind = lsp::SymbolKind::EnumMember;
child.deprecated = false;
- child.range.start = GodotPosition(value.line, value.leftmost_column).to_lsp(this->lines);
- child.range.end = GodotPosition(value.line, value.rightmost_column).to_lsp(this->lines);
+ child.range.start = GodotPosition(value.line, value.leftmost_column).to_lsp(lines);
+ child.range.end = GodotPosition(value.line, value.rightmost_column).to_lsp(lines);
child.selectionRange = range_of_node(value.identifier);
child.documentation = value.doc_data.description;
child.uri = uri;
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 0b1371851b..9bf458e031 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -114,7 +114,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
scr->update_exports();
ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
- ScriptEditor::get_singleton()->trigger_live_script_reload();
+ ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path());
}
}
@@ -421,7 +421,7 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
List<const lsp::DocumentSymbol *> symbols;
- Array arr = this->find_symbols(params, symbols);
+ Array arr = find_symbols(params, symbols);
return arr;
}
@@ -429,7 +429,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
List<const lsp::DocumentSymbol *> symbols;
- Array arr = this->find_symbols(params, symbols);
+ Array arr = find_symbols(params, symbols);
if (arr.is_empty() && !symbols.is_empty() && !symbols.front()->get()->native_class.is_empty()) { // Find a native symbol
const lsp::DocumentSymbol *symbol = symbols.front()->get();
if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) {
@@ -456,7 +456,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
id = "class_global:" + symbol->native_class + ":" + symbol->name;
break;
}
- call_deferred(SNAME("show_native_symbol_in_editor"), id);
+ callable_mp(this, &GDScriptTextDocument::show_native_symbol_in_editor).call_deferred(id);
} else {
notify_client_show_symbol(symbol);
}
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 605e82be6e..5ff1c78ac9 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -34,6 +34,7 @@
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
+#include "gdscript_tokenizer_buffer.h"
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
@@ -83,18 +84,33 @@ class EditorExportGDScript : public EditorExportPlugin {
public:
virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override {
- String script_key;
+ int script_mode = EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED;
const Ref<EditorExportPreset> &preset = get_export_preset();
if (preset.is_valid()) {
- script_key = preset->get_script_encryption_key().to_lower();
+ script_mode = preset->get_script_export_mode();
}
- if (!p_path.ends_with(".gd")) {
+ if (!p_path.ends_with(".gd") || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) {
return;
}
+ Vector<uint8_t> file = FileAccess::get_file_as_bytes(p_path);
+ if (file.is_empty()) {
+ return;
+ }
+
+ String source;
+ source.parse_utf8(reinterpret_cast<const char *>(file.ptr()), file.size());
+ GDScriptTokenizerBuffer::CompressMode compress_mode = script_mode == EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED ? GDScriptTokenizerBuffer::COMPRESS_ZSTD : GDScriptTokenizerBuffer::COMPRESS_NONE;
+ file = GDScriptTokenizerBuffer::parse_code_string(source, compress_mode);
+ if (file.is_empty()) {
+ return;
+ }
+
+ add_file(p_path.get_basename() + ".gdc", file, true);
+
return;
}
@@ -185,6 +201,10 @@ void test_tokenizer() {
GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER);
}
+void test_tokenizer_buffer() {
+ GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER_BUFFER);
+}
+
void test_parser() {
GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER);
}
@@ -198,6 +218,7 @@ void test_bytecode() {
}
REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer);
+REGISTER_TEST_COMMAND("gdscript-tokenizer-buffer", &test_tokenizer_buffer);
REGISTER_TEST_COMMAND("gdscript-parser", &test_parser);
REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler);
REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode);
diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md
index 361d586d32..72b5316532 100644
--- a/modules/gdscript/tests/README.md
+++ b/modules/gdscript/tests/README.md
@@ -6,3 +6,44 @@ and output files.
See the
[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript)
for information about creating and running GDScript integration tests.
+
+# GDScript Autocompletion tests
+
+The `script/completion` folder contains test for the GDScript autocompletion.
+
+Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked.
+
+The config file contains two section:
+
+`[input]` contains keys that configure the test environment. The following keys are possible:
+
+- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build.
+- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test.
+- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened.
+
+`[output]` specifies the expected results for the test. The following key are supported:
+
+- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice.
+- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`.
+- `call_hint: String`: The expected call hint returned by autocompletion.
+- `forced: boolean`: Whether autocompletion is expected to force opening a completion window.
+
+Tests will only test against entries in `[output]` that were specified.
+
+## Writing autocompletion tests
+
+To avoid failing edge cases a certain behavior needs to be tested multiple times. Some things that tests should account for:
+
+- All possible types: Test with all possible types that apply to the tested behavior. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
+
+ - `BUILTIN`
+ - `NATIVE`
+ - GDScripts (with `class_name` as well as `preload`ed)
+ - C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed)
+ - Autoloads
+
+- Possible contexts: the completion might be placed in different places of the program. e.g:
+ - initializers of class members
+ - directly inside a suite
+ - assignments inside a suite
+ - as parameter to a call
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 361ca276bb..a0329eb8d2 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_analyzer.h"
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/core_globals.h"
@@ -131,10 +132,11 @@ void finish_language() {
StringName GDScriptTestRunner::test_function_name;
-GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) {
+GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames, bool p_use_binary_tokens) {
test_function_name = StaticCString::create("test");
do_init_languages = p_init_language;
print_filenames = p_print_filenames;
+ binary_tokens = p_use_binary_tokens;
source_dir = p_source_dir;
if (!source_dir.ends_with("/")) {
@@ -266,7 +268,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
while (!next.is_empty()) {
if (dir->current_is_dir()) {
- if (next == "." || next == "..") {
+ if (next == "." || next == ".." || next == "completion" || next == "lsp") {
next = dir->get_next();
continue;
}
@@ -277,6 +279,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
+ } else if (binary_tokens && next.ends_with(".textonly.gd")) {
+ next = dir->get_next();
+ continue;
} else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
@@ -299,6 +304,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
}
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
+ if (binary_tokens) {
+ test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
+ }
tests.push_back(test);
}
}
@@ -321,24 +329,65 @@ bool GDScriptTestRunner::make_tests() {
return make_tests_for_dir(dir->get_current_dir());
}
-bool GDScriptTestRunner::generate_class_index() {
+static bool generate_class_index_recursive(const String &p_dir) {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
+
+ if (err != OK) {
+ return false;
+ }
+
+ String current_dir = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
- for (int i = 0; i < tests.size(); i++) {
- GDScriptTest test = tests[i];
- String base_type;
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == ".." || next == "completion" || next == "lsp") {
+ next = dir->get_next();
+ continue;
+ }
+ if (!generate_class_index_recursive(current_dir.path_join(next))) {
+ return false;
+ }
+ } else {
+ if (!next.ends_with(".gd")) {
+ next = dir->get_next();
+ continue;
+ }
+ String base_type;
+ String source_file = current_dir.path_join(next);
+ String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
+ if (class_name.is_empty()) {
+ next = dir->get_next();
+ continue;
+ }
+ ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
+ "Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
- String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type);
- if (class_name.is_empty()) {
- continue;
+ ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
}
- ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
- "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name));
- ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file());
+ next = dir->get_next();
}
+
+ dir->list_dir_end();
+
return true;
}
+bool GDScriptTestRunner::generate_class_index() {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
+
+ ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
+
+ source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
+ return generate_class_index_recursive(dir->get_current_dir());
+}
+
GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
source_file = p_source_path;
output_file = p_output_path;
@@ -484,7 +533,15 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
Ref<GDScript> script;
script.instantiate();
script->set_path(source_file);
- err = script->load_source_code(source_file);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = script->load_source_code(source_file);
+ } else {
+ String code = FileAccess::get_file_as_string(source_file, &err);
+ if (!err) {
+ Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
+ script->set_binary_tokens_source(buffer);
+ }
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_LOAD_ERROR;
@@ -494,7 +551,11 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
// Test parsing.
GDScriptParser parser;
- err = parser.parse(script->get_source_code(), source_file, false);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = parser.parse(script->get_source_code(), source_file, false);
+ } else {
+ err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_PARSER_ERROR;
@@ -583,7 +644,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
add_print_handler(&_print_handler);
add_error_handler(&_error_handler);
- script->reload();
+ err = script->reload();
+ if (err) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.output = "";
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
+ }
// Create object instance for test.
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h
index b1190604ad..57e3ac86f9 100644
--- a/modules/gdscript/tests/gdscript_test_runner.h
+++ b/modules/gdscript/tests/gdscript_test_runner.h
@@ -62,6 +62,11 @@ public:
bool passed;
};
+ enum TokenizerMode {
+ TOKENIZER_TEXT,
+ TOKENIZER_BUFFER,
+ };
+
private:
struct ErrorHandlerData {
TestResult *result = nullptr;
@@ -79,6 +84,8 @@ private:
PrintHandlerList _print_handler;
ErrorHandlerList _error_handler;
+ TokenizerMode tokenizer_mode = TOKENIZER_TEXT;
+
void enable_stdout();
void disable_stdout();
bool check_output(const String &p_output) const;
@@ -96,6 +103,9 @@ public:
const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); }
const String &get_output_file() const { return output_file; }
+ void set_tokenizer_mode(TokenizerMode p_tokenizer_mode) { tokenizer_mode = p_tokenizer_mode; }
+ TokenizerMode get_tokenizer_mode() const { return tokenizer_mode; }
+
GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir);
GDScriptTest() :
GDScriptTest(String(), String(), String()) {} // Needed to use in Vector.
@@ -108,6 +118,7 @@ class GDScriptTestRunner {
bool is_generating = false;
bool do_init_languages = false;
bool print_filenames; // Whether filenames should be printed when generated/running tests
+ bool binary_tokens; // Test with buffer tokenizer.
bool make_tests();
bool make_tests_for_dir(const String &p_dir);
@@ -120,7 +131,7 @@ public:
int run_tests();
bool generate_outputs();
- GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false);
+ GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false, bool p_use_binary_tokens = false);
~GDScriptTestRunner();
};
diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h
index 5fd7d942d2..5acf436e42 100644
--- a/modules/gdscript/tests/gdscript_test_runner_suite.h
+++ b/modules/gdscript/tests/gdscript_test_runner_suite.h
@@ -38,12 +38,10 @@
namespace GDScriptTests {
TEST_SUITE("[Modules][GDScript]") {
- // GDScript 2.0 is still under heavy construction.
- // Allow the tests to fail, but do not ignore errors during development.
- // Update the scripts and expected output as needed.
TEST_CASE("Script compilation and runtime") {
bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr;
- GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames);
+ bool use_binary_tokens = OS::get_singleton()->get_cmdline_args().find("--use-binary-tokens") != nullptr;
+ GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames, use_binary_tokens);
int fail_count = runner.run_tests();
INFO("Make sure `*.out` files have expected results.");
REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass.");
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
index 73a54d7820..a6a3973255 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_constants.gd
>> 8
->> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
index 92e7b9316e..70fdc5b62c 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_constants_as_variant.gd
>> 9
->> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
index 892f8e2c3f..6632f056bd 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_instance_constants.gd
>> 8
->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
index 8257e74f57..0459b756d1 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_instance_constants_as_variant.gd
>> 9
->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'.
diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
new file mode 100644
index 0000000000..27e695d245
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+ {"display": "autoplay"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd
new file mode 100644
index 0000000000..d41bbb970c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd
@@ -0,0 +1,6 @@
+extends Node
+
+var test: AnimationPlayer = $AnimationPlayer
+
+func _ready():
+ test.➡
diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.gd
index 53d0b14d72..53d0b14d72 100644
--- a/modules/gdscript/tests/scripts/lsp/class.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/class.gd
diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.gd
index 38b9ec110a..38b9ec110a 100644
--- a/modules/gdscript/tests/scripts/lsp/enums.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/enums.gd
diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.gd
index c25d73a719..c25d73a719 100644
--- a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/indentation.gd
diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.gd
index 6f5d468eea..6f5d468eea 100644
--- a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/lambdas.gd
diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.gd
index b6cc46f7da..b6cc46f7da 100644
--- a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/local_variables.gd
diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.gd
index 8dfaee2e5b..8dfaee2e5b 100644
--- a/modules/gdscript/tests/scripts/lsp/properties.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/properties.gd
diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.gd
index 20b8fb9bd7..20b8fb9bd7 100644
--- a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/scopes.gd
diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd
index 338000fa0e..338000fa0e 100644
--- a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
index 9ad77f1432..9ad77f1432 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
index 31bed2dbc7..31bed2dbc7 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
new file mode 100644
index 0000000000..b744e6170b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
@@ -0,0 +1,11 @@
+func test():
+ var i: Variant = 123
+ var s: Variant = "str"
+ prints(i is int, i is not int)
+ prints(s is int, s is not int)
+
+ var a: Variant = false
+ var b: Variant = true
+ prints(a == b is int, a == b is not int)
+ prints(a == (b is int), a == (b is not int))
+ prints((a == b) is int, (a == b) is not int)
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.out b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
new file mode 100644
index 0000000000..f0535f9c83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+true false
+false true
+true false
+true false
+false true
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
index 86152f4543..7b82d9b1da 100644
--- a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
@@ -9,6 +9,7 @@ func test():
# Alternatively, backslashes can be used.
if 1 == 1 \
+ \
and 2 == 2 and \
3 == 3:
pass
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
index 9e4b360fb2..82616ee3cf 100644
--- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
@@ -6,6 +6,9 @@ var property:
set(value):
_backing = value - 1000
+var property_2:
+ get(): # Allow parentheses.
+ return 123
func test():
print("Not using self:")
@@ -35,3 +38,5 @@ func test():
self.property = 5000
print(self.property)
print(self._backing)
+
+ print(property_2)
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
index 560e0c3bd7..23f98f44ab 100644
--- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
@@ -17,3 +17,4 @@ Using self:
-50
5000
4000
+123
diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot
index 25b49c0abd..c500ef443d 100644
--- a/modules/gdscript/tests/scripts/project.godot
+++ b/modules/gdscript/tests/scripts/project.godot
@@ -3,7 +3,7 @@
; It also helps for opening Godot to edit the scripts, but please don't
; let the editor changes be saved.
-config_version=4
+config_version=5
[application]
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
index 2a97eaea44..c524a1ae6b 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> runtime/errors/constant_array_is_deep.gd
>> 6
->> Invalid set index '0' (on base: 'Dictionary') with value of type 'int'
+>> Invalid assignment of property or key '0' with value of type 'int' on a base object of type 'Dictionary'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
index c807db6b0c..cf51b0262d 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> runtime/errors/constant_dictionary_is_deep.gd
>> 6
->> Invalid set index '0' (on base: 'Array') with value of type 'int'
+>> Invalid assignment of index '0' (on base: 'Array') with value of type 'int'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd
new file mode 100644
index 0000000000..e4016c0119
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd
@@ -0,0 +1,6 @@
+func test():
+ var array: Array = [1, 2, 3]
+ print(array)
+ var callable: Callable = array.clear
+ callable.call()
+ print(array)
diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out
new file mode 100644
index 0000000000..c4182b38e9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+[1, 2, 3]
+[]
diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd
new file mode 100644
index 0000000000..b9746a8207
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd
@@ -0,0 +1,10 @@
+func test():
+ var node := Node.new()
+ var callable: Callable = node.free
+ callable.call()
+ print(node)
+
+ node = Node.new()
+ callable = node["free"]
+ callable.call()
+ print(node)
diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out
new file mode 100644
index 0000000000..97bfc46d96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+<Freed Object>
+<Freed Object>
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
index f6aa58737f..97e9da3b26 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
@@ -1,12 +1,18 @@
-# GH-79521
+# GH-79521, GH-86032
class_name TestStaticMethodAsCallable
static func static_func() -> String:
return "Test"
+static func another_static_func():
+ prints("another_static_func:", static_func.call(), static_func.is_valid())
+
func test():
var a: Callable = TestStaticMethodAsCallable.static_func
var b: Callable = static_func
prints(a.call(), a.is_valid())
prints(b.call(), b.is_valid())
+ @warning_ignore("static_called_on_instance")
+ another_static_func()
+ TestStaticMethodAsCallable.another_static_func()
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
index e6d461b8f9..2b773ce8ee 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
+++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
@@ -1,3 +1,5 @@
GDTEST_OK
Test true
Test true
+another_static_func: Test true
+another_static_func: Test true
diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd
new file mode 100644
index 0000000000..11f064bb83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd
@@ -0,0 +1,10 @@
+func test():
+ print(print)
+ print(len)
+
+ prints.callv([1, 2, 3])
+ print(mini.call(1, 2))
+ print(len.bind("abc").call())
+
+ const ABSF = absf
+ print(ABSF.call(-1.2))
diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out
new file mode 100644
index 0000000000..91549b9345
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+@GlobalScope::print (Callable)
+@GDScript::len (Callable)
+1 2 3
+1
+3
+1.2
diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h
new file mode 100644
index 0000000000..fd6b5321e6
--- /dev/null
+++ b/modules/gdscript/tests/test_completion.h
@@ -0,0 +1,203 @@
+/**************************************************************************/
+/* test_completion.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_COMPLETION_H
+#define TEST_COMPLETION_H
+
+#ifdef TOOLS_ENABLED
+
+#include "core/io/config_file.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/object/script_language.h"
+#include "core/variant/dictionary.h"
+#include "core/variant/variant.h"
+#include "gdscript_test_runner.h"
+#include "modules/modules_enabled.gen.h" // For mono.
+#include "scene/resources/packed_scene.h"
+
+#include "../gdscript.h"
+#include "tests/test_macros.h"
+
+#include "editor/editor_settings.h"
+#include "scene/theme/theme_db.h"
+
+namespace GDScriptTests {
+
+static bool match_option(const Dictionary p_expected, const ScriptLanguage::CodeCompletionOption p_got) {
+ if (p_expected.get("display", p_got.display) != p_got.display) {
+ return false;
+ }
+ if (p_expected.get("insert_text", p_got.insert_text) != p_got.insert_text) {
+ return false;
+ }
+ if (p_expected.get("kind", p_got.kind) != Variant(p_got.kind)) {
+ return false;
+ }
+ if (p_expected.get("location", p_got.location) != Variant(p_got.location)) {
+ return false;
+ }
+ return true;
+}
+
+static void to_dict_list(Variant p_variant, List<Dictionary> &p_list) {
+ ERR_FAIL_COND(!p_variant.is_array());
+
+ Array arr = p_variant;
+ for (int i = 0; i < arr.size(); i++) {
+ if (arr[i].get_type() == Variant::DICTIONARY) {
+ p_list.push_back(arr[i]);
+ }
+ }
+}
+
+static void test_directory(const String &p_dir) {
+ Error err = OK;
+ Ref<DirAccess> dir = DirAccess::open(p_dir, &err);
+
+ if (err != OK) {
+ FAIL("Invalid test directory.");
+ return;
+ }
+
+ String path = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == "..") {
+ next = dir->get_next();
+ continue;
+ }
+ test_directory(path.path_join(next));
+ } else if (next.ends_with(".gd") && !next.ends_with(".notest.gd")) {
+ Ref<FileAccess> acc = FileAccess::open(path.path_join(next), FileAccess::READ, &err);
+
+ if (err != OK) {
+ next = dir->get_next();
+ continue;
+ }
+
+ String code = acc->get_as_utf8_string();
+ // For ease of reading ➡ (0x27A1) acts as sentinel char instead of 0xFFFF in the files.
+ code = code.replace_first(String::chr(0x27A1), String::chr(0xFFFF));
+ // Require pointer sentinel char in scripts.
+ CHECK(code.find_char(0xFFFF) != -1);
+
+ ConfigFile conf;
+ if (conf.load(path.path_join(next.get_basename() + ".cfg")) != OK) {
+ FAIL("No config file found.");
+ }
+
+#ifndef MODULE_MONO_ENABLED
+ if (conf.get_value("input", "cs", false)) {
+ next = dir->get_next();
+ continue;
+ }
+#endif
+
+ EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false));
+
+ List<Dictionary> include;
+ to_dict_list(conf.get_value("output", "include", Array()), include);
+
+ List<Dictionary> exclude;
+ to_dict_list(conf.get_value("output", "exclude", Array()), exclude);
+
+ List<ScriptLanguage::CodeCompletionOption> options;
+ String call_hint;
+ bool forced;
+
+ Node *owner = nullptr;
+ if (conf.has_section_key("input", "scene")) {
+ Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene");
+ if (scene.is_valid()) {
+ owner = scene->instantiate();
+ }
+ } else if (dir->file_exists(next.get_basename() + ".tscn")) {
+ Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene");
+ if (scene.is_valid()) {
+ owner = scene->instantiate();
+ }
+ }
+
+ GDScriptLanguage::get_singleton()->complete_code(code, path.path_join(next), owner, &options, forced, call_hint);
+ String contains_excluded;
+ for (ScriptLanguage::CodeCompletionOption &option : options) {
+ for (const Dictionary &E : exclude) {
+ if (match_option(E, option)) {
+ contains_excluded = option.display;
+ break;
+ }
+ }
+ if (!contains_excluded.is_empty()) {
+ break;
+ }
+
+ for (const Dictionary &E : include) {
+ if (match_option(E, option)) {
+ include.erase(E);
+ break;
+ }
+ }
+ }
+ CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'.");
+ CHECK(include.is_empty());
+
+ String expected_call_hint = conf.get_value("output", "call_hint", call_hint);
+ bool expected_forced = conf.get_value("output", "forced", forced);
+
+ CHECK(expected_call_hint == call_hint);
+ CHECK(expected_forced == forced);
+
+ if (owner) {
+ memdelete(owner);
+ }
+ }
+ next = dir->get_next();
+ }
+}
+
+TEST_SUITE("[Modules][GDScript][Completion]") {
+ TEST_CASE("[Editor] Check suggestion list") {
+ // Set all editor settings that code completion relies on.
+ EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", false);
+ init_language("modules/gdscript/tests/scripts");
+
+ test_directory("modules/gdscript/tests/scripts/completion");
+ }
+}
+} // namespace GDScriptTests
+
+#endif
+
+#endif // TEST_COMPLETION_H
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 467bedc4b2..f6965cf7cf 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
#include "../gdscript_tokenizer.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -50,7 +51,7 @@
namespace GDScriptTests {
static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
int tab_size = 4;
@@ -107,6 +108,53 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines)
print_line(current.get_name()); // Should be EOF
}
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines);
+
+static void test_tokenizer_buffer(const String &p_code, const Vector<String> &p_lines) {
+ Vector<uint8_t> binary = GDScriptTokenizerBuffer::parse_code_string(p_code, GDScriptTokenizerBuffer::COMPRESS_NONE);
+ test_tokenizer_buffer(binary, p_lines);
+}
+
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines) {
+ GDScriptTokenizerBuffer tokenizer;
+ tokenizer.set_code_buffer(p_buffer);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
GDScriptParser parser;
Error err = parser.parse(p_code, p_script_path, false);
@@ -119,7 +167,7 @@ static void test_parser(const String &p_code, const String &p_script_path, const
}
GDScriptAnalyzer analyzer(&parser);
- analyzer.analyze();
+ err = analyzer.analyze();
if (err != OK) {
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
@@ -212,7 +260,7 @@ void test(TestType p_type) {
}
String test = cmdlargs.back()->get();
- if (!test.ends_with(".gd")) {
+ if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
return;
}
@@ -255,6 +303,13 @@ void test(TestType p_type) {
case TEST_TOKENIZER:
test_tokenizer(code, lines);
break;
+ case TEST_TOKENIZER_BUFFER:
+ if (test.ends_with(".gdc")) {
+ test_tokenizer_buffer(buf, lines);
+ } else {
+ test_tokenizer_buffer(code, lines);
+ }
+ break;
case TEST_PARSER:
test_parser(code, test, lines);
break;
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
index b39dfe2b5a..32f278d5ce 100644
--- a/modules/gdscript/tests/test_gdscript.h
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -39,6 +39,7 @@ namespace GDScriptTests {
enum TestType {
TEST_TOKENIZER,
+ TEST_TOKENIZER_BUFFER,
TEST_PARSER,
TEST_COMPILER,
TEST_BYTECODE,
diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h
index e57df00e2d..6192272f80 100644
--- a/modules/gdscript/tests/test_lsp.h
+++ b/modules/gdscript/tests/test_lsp.h
@@ -76,7 +76,7 @@ namespace GDScriptTests {
// LSP GDScript test scripts are located inside project of other GDScript tests:
// Cannot reset `ProjectSettings` (singleton) -> Cannot load another workspace and resources in there.
// -> Reuse GDScript test project. LSP specific scripts are then placed inside `lsp` folder.
-// Access via `res://lsp/my_script.notest.gd`.
+// Access via `res://lsp/my_script.gd`.
const String root = "modules/gdscript/tests/scripts/";
/*
@@ -394,7 +394,7 @@ func f():
Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
{
- String path = "res://lsp/local_variables.notest.gd";
+ String path = "res://lsp/local_variables.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -413,7 +413,7 @@ func f():
}
SUBCASE("Can get correct ranges for indented variables") {
- String path = "res://lsp/indentation.notest.gd";
+ String path = "res://lsp/indentation.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -421,7 +421,7 @@ func f():
}
SUBCASE("Can get correct ranges for scopes") {
- String path = "res://lsp/scopes.notest.gd";
+ String path = "res://lsp/scopes.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -429,7 +429,7 @@ func f():
}
SUBCASE("Can get correct ranges for lambda") {
- String path = "res://lsp/lambdas.notest.gd";
+ String path = "res://lsp/lambdas.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -437,7 +437,7 @@ func f():
}
SUBCASE("Can get correct ranges for inner class") {
- String path = "res://lsp/class.notest.gd";
+ String path = "res://lsp/class.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -445,7 +445,7 @@ func f():
}
SUBCASE("Can get correct ranges for inner class") {
- String path = "res://lsp/enums.notest.gd";
+ String path = "res://lsp/enums.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -453,7 +453,7 @@ func f():
}
SUBCASE("Can get correct ranges for shadowing & shadowed variables") {
- String path = "res://lsp/shadowing_initializer.notest.gd";
+ String path = "res://lsp/shadowing_initializer.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -461,7 +461,7 @@ func f():
}
SUBCASE("Can get correct ranges for properties and getter/setter") {
- String path = "res://lsp/properties.notest.gd";
+ String path = "res://lsp/properties.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub
index a34a97d230..3068377e60 100644
--- a/modules/glslang/SCsub
+++ b/modules/glslang/SCsub
@@ -43,7 +43,6 @@ if env["builtin_glslang"]:
"glslang/MachineIndependent/SymbolTable.cpp",
"glslang/MachineIndependent/Versions.cpp",
"glslang/ResourceLimits/ResourceLimits.cpp",
- "OGLCompilersDLL/InitializeDll.cpp",
"SPIRV/disassemble.cpp",
"SPIRV/doc.cpp",
"SPIRV/GlslangToSpv.cpp",
diff --git a/modules/glslang/config.py b/modules/glslang/config.py
index d22f9454ed..1169776a73 100644
--- a/modules/glslang/config.py
+++ b/modules/glslang/config.py
@@ -1,5 +1,7 @@
def can_build(env, platform):
- return True
+ # glslang is only needed when Vulkan or Direct3D 12-based renderers are available,
+ # as OpenGL doesn't use glslang.
+ return env["vulkan"] or env["d3d12"]
def configure(env):
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index d7e5ddd32c..09ad1d6777 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -39,7 +39,7 @@
#include <glslang/SPIRV/GlslangToSpv.h>
static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage, const String &p_source_code, RenderingDevice::ShaderLanguage p_language, String *r_error, const RenderingDevice *p_render_device) {
- const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
+ const RDD::Capabilities &capabilities = p_render_device->get_device_capabilities();
Vector<uint8_t> ret;
ERR_FAIL_COND_V(p_language == RenderingDevice::SHADER_LANGUAGE_HLSL, ret);
@@ -57,17 +57,17 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
- if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
- if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
+ if (capabilities.device_family == RDD::DEVICE_VULKAN) {
+ if (capabilities.version_major == 1 && capabilities.version_minor == 0) {
ClientVersion = glslang::EShTargetVulkan_1_0;
TargetVersion = glslang::EShTargetSpv_1_0;
- } else if (capabilities->version_major == 1 && capabilities->version_minor == 1) {
+ } else if (capabilities.version_major == 1 && capabilities.version_minor == 1) {
ClientVersion = glslang::EShTargetVulkan_1_1;
TargetVersion = glslang::EShTargetSpv_1_3;
} else {
// use defaults
}
- } else if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_DIRECTX) {
+ } else if (capabilities.device_family == RDD::DEVICE_DIRECTX) {
// NIR-DXIL is Vulkan 1.1-conformant.
ClientVersion = glslang::EShTargetVulkan_1_1;
// The SPIR-V part of Mesa supports 1.6, but:
@@ -186,9 +186,9 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
}
static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
- const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
+ const RenderingDeviceDriver::Capabilities &capabilities = p_render_device->get_device_capabilities();
String version;
- version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities.version_major) + ", minor=" + itos(capabilities.version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
return version;
}
diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
index 24f6dbd887..e3433e6e29 100644
--- a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
+++ b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Imports Blender scenes in the [code].blend[/code] file format through the glTF 2.0 3D import pipeline. This importer requires Blender to be installed by the user, so that it can be used to export the scene as glTF 2.0.
- The location of the Blender binary is set via the [code]filesystem/import/blender/blender3_path[/code] editor setting.
+ The location of the Blender binary is set via the [code]filesystem/import/blender/blender_path[/code] editor setting.
This importer is only used if [member ProjectSettings.filesystem/import/blender/enabled] is enabled, otherwise [code].blend[/code] files present in the project folder are not imported.
Blend import requires Blender 3.0.
Internally, the EditorSceneFormatImporterBlend uses the Blender glTF "Use Original" mode to reference external textures.
diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml
index f678a11319..ba7323b7cd 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -1,14 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFAccessor" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Represents a GLTF accessor.
</brief_description>
<description>
+ GLTFAccessor is a data structure representing GLTF a [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view.
+ Most custom data stored in GLTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU.
</description>
<tutorials>
+ <link title="Buffers, BufferViews, and Accessors in Khronos glTF specification">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md</link>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
<member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1">
+ The index of the buffer view this accessor is referencing. If [code]-1[/code], this accessor is not referencing any buffer view.
</member>
<member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0">
</member>
diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml
index d0f76a9af3..11d58bda72 100644
--- a/modules/gltf/doc_classes/GLTFBufferView.xml
+++ b/modules/gltf/doc_classes/GLTFBufferView.xml
@@ -1,22 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFBufferView" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Represents a GLTF buffer view.
</brief_description>
<description>
+ GLTFBufferView is a data structure representing GLTF a [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer.
+ Most custom uses of buffers only need to use the [member buffer], [member byte_length], and [member byte_offset]. The [member byte_stride] and [member indices] properties are for more advanced use cases such as interleaved mesh data encoded for the GPU.
</description>
<tutorials>
+ <link title="Buffers, BufferViews, and Accessors in Khronos glTF specification">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md</link>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
+ <methods>
+ <method name="load_buffer_view_data" qualifiers="const">
+ <return type="PackedByteArray" />
+ <param index="0" name="state" type="GLTFState" />
+ <description>
+ Loads the buffer view data from the buffer referenced by this buffer view in the given [GLTFState]. Interleaved data with a byte stride is not yet supported by this method. The data is returned as a [PackedByteArray].
+ </description>
+ </method>
+ </methods>
<members>
<member name="buffer" type="int" setter="set_buffer" getter="get_buffer" default="-1">
+ The index of the buffer this buffer view is referencing. If [code]-1[/code], this buffer view is not referencing any buffer.
</member>
<member name="byte_length" type="int" setter="set_byte_length" getter="get_byte_length" default="0">
+ The length, in bytes, of this buffer view. If [code]0[/code], this buffer view is empty.
</member>
<member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0">
+ The offset, in bytes, from the start of the buffer to the start of this buffer view.
</member>
<member name="byte_stride" type="int" setter="set_byte_stride" getter="get_byte_stride" default="-1">
+ The stride, in bytes, between interleaved data. If [code]-1[/code], this buffer view is not interleaved.
</member>
<member name="indices" type="bool" setter="set_indices" getter="get_indices" default="false">
+ True if the GLTFBufferView's OpenGL GPU buffer type is an [code]ELEMENT_ARRAY_BUFFER[/code] used for vertex indices (integer constant [code]34963[/code]). False if the buffer type is [code]ARRAY_BUFFER[/code] used for vertex attributes (integer constant [code]34962[/code]) or when any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set but never used, setting this property will do nothing.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index f5066ba4f1..1b52a82298 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -84,7 +84,7 @@
<param index="1" name="path" type="String" />
<description>
Takes a [GLTFState] object through the [param state] parameter and writes a glTF file to the filesystem.
- [b]Note:[/b] The extension of the glTF file determines if it is a .glb binary file or a .gltf file.
+ [b]Note:[/b] The extension of the glTF file determines if it is a .glb binary file or a .gltf text file.
</description>
</method>
</methods>
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index aaa55e772a..0eabcb5022 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -30,7 +30,7 @@
<param index="3" name="node" type="Node" />
<description>
Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json].
- This method can be used to modify the final JSON of each node.
+ This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting GLTF data not generated from a Godot scene.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
@@ -114,7 +114,7 @@
<param index="0" name="state" type="GLTFState" />
<description>
Part of the import process. This method is run after [method _parse_node_extensions] and before [method _generate_scene_node].
- This method can be used to modify any of the data imported so far, including any scene nodes, before running the final per-node import step.
+ This method can be used to modify any of the data imported so far after parsing, before generating the nodes and then running the final per-node import step.
</description>
</method>
<method name="_import_preflight" qualifiers="virtual">
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index cf39721ce8..ca66cd54b0 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -4,7 +4,7 @@
Represents a GLTF physics body.
</brief_description>
<description>
- Represents a physics body as defined by the [code]OMI_physics_body[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
@@ -15,7 +15,7 @@
<return type="GLTFPhysicsBody" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
- Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary].
+ Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format.
</description>
</method>
<method name="from_node" qualifiers="static">
@@ -28,7 +28,7 @@
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
- Serializes this GLTFPhysicsBody instance into a [Dictionary].
+ Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension.
</description>
</method>
<method name="to_node" qualifiers="const">
@@ -42,13 +42,20 @@
<member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" default="Vector3(0, 0, 0)">
The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle".
</member>
- <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;static&quot;">
- The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
+ <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;rigid&quot;">
+ The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "animatable", "character", "rigid", "vehicle", and "trigger". When exporting, this will be squashed down to one of "static", "kinematic", or "dynamic" motion types, or the "trigger" property.
</member>
<member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)">
The center of mass of the body, in meters. This is in local space relative to the body. By default, the center of the mass is the body's origin.
</member>
- <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)">
+ <member name="inertia_diagonal" type="Vector3" setter="set_inertia_diagonal" getter="get_inertia_diagonal" default="Vector3(0, 0, 0)">
+ The inertia strength of the physics body, in kilogram meter squared (kg⋅m²). This represents the inertia around the principle axes, the diagonal of the inertia tensor matrix. This is only used when the body type is "rigid" or "vehicle".
+ When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
+ </member>
+ <member name="inertia_orientation" type="Quaternion" setter="set_inertia_orientation" getter="get_inertia_orientation" default="Quaternion(0, 0, 0, 1)">
+ The inertia orientation of the physics body. This defines the rotation of the inertia's principle axes relative to the object's local axes. This is only used when the body type is "rigid" or "vehicle" and [member inertia_diagonal] is set to a non-zero value.
+ </member>
+ <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)" is_deprecated="true">
The inertia tensor of the physics body, in kilogram meter squared (kg⋅m²). This is only used when the body type is "rigid" or "vehicle".
When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
</member>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
index 67382f3295..c397c660d9 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
@@ -4,11 +4,12 @@
Represents a GLTF physics shape.
</brief_description>
<description>
- Represents a physics shape as defined by the [code]OMI_collider[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider</link>
+ <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link>
+ <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
@@ -28,7 +29,7 @@
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
- Serializes this GLTFPhysicsShape instance into a [Dictionary].
+ Serializes this GLTFPhysicsShape instance into a [Dictionary] in the format defined by [code]OMI_physics_shape[/code].
</description>
</method>
<method name="to_node">
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index e256041737..100750f400 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -20,6 +20,14 @@
Appends an extension to the list of extensions used by this GLTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically.
</description>
</method>
+ <method name="append_data_to_buffers">
+ <return type="int" />
+ <param index="0" name="data" type="PackedByteArray" />
+ <param index="1" name="deduplication" type="bool" />
+ <description>
+ Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is true, the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
+ </description>
+ </method>
<method name="get_accessors">
<return type="GLTFAccessor[]" />
<description>
diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp
index 659a60e6a1..330310d92a 100644
--- a/modules/gltf/editor/editor_import_blend_runner.cpp
+++ b/modules/gltf/editor/editor_import_blend_runner.cpp
@@ -153,13 +153,7 @@ String dict_to_xmlrpc(const Dictionary &p_dict) {
}
Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) {
- String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
-
-#ifdef WINDOWS_ENABLED
- blender_path = blender_path.path_join("blender.exe");
-#else
- blender_path = blender_path.path_join("blender");
-#endif
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
List<String> args;
args.push_back("--background");
@@ -198,6 +192,40 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) {
}
}
+HTTPClient::Status EditorImportBlendRunner::connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs) {
+ p_client->connect_to_host("127.0.0.1", rpc_port);
+ HTTPClient::Status status = p_client->get_status();
+
+ int attempts = 1;
+ int wait_usecs = 1000;
+
+ bool done = false;
+ while (!done) {
+ OS::get_singleton()->delay_usec(wait_usecs);
+ status = p_client->get_status();
+ switch (status) {
+ case HTTPClient::STATUS_RESOLVING:
+ case HTTPClient::STATUS_CONNECTING: {
+ p_client->poll();
+ break;
+ }
+ case HTTPClient::STATUS_CONNECTED: {
+ done = true;
+ break;
+ }
+ default: {
+ if (attempts * wait_usecs < p_timeout_usecs) {
+ p_client->connect_to_host("127.0.0.1", rpc_port);
+ } else {
+ return status;
+ }
+ }
+ }
+ }
+
+ return status;
+}
+
Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
kill_timer->stop();
@@ -217,25 +245,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
// Connect to RPC server.
Ref<HTTPClient> client = HTTPClient::create();
- client->connect_to_host("127.0.0.1", rpc_port);
-
- bool done = false;
- while (!done) {
- HTTPClient::Status status = client->get_status();
- switch (status) {
- case HTTPClient::STATUS_RESOLVING:
- case HTTPClient::STATUS_CONNECTING: {
- client->poll();
- break;
- }
- case HTTPClient::STATUS_CONNECTED: {
- done = true;
- break;
- }
- default: {
- ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status));
- }
- }
+ HTTPClient::Status status = connect_blender_rpc(client, 1000000);
+ if (status != HTTPClient::STATUS_CONNECTED) {
+ ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status));
}
// Send XML request.
@@ -246,9 +258,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
}
// Wait for response.
- done = false;
+ bool done = false;
while (!done) {
- HTTPClient::Status status = client->get_status();
+ status = client->get_status();
switch (status) {
case HTTPClient::STATUS_REQUESTING: {
client->poll();
diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h
index b2b82394e1..626f3c9eba 100644
--- a/modules/gltf/editor/editor_import_blend_runner.h
+++ b/modules/gltf/editor/editor_import_blend_runner.h
@@ -33,6 +33,7 @@
#ifdef TOOLS_ENABLED
+#include "core/io/http_client.h"
#include "core/os/os.h"
#include "scene/main/node.h"
#include "scene/main/timer.h"
@@ -60,6 +61,7 @@ public:
bool is_running() { return blender_pid != 0 && OS::get_singleton()->is_process_running(blender_pid); }
bool is_using_rpc() { return rpc_port != 0; }
Error do_import(const Dictionary &p_options);
+ HTTPClient::Status connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs);
EditorImportBlendRunner();
};
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 83c7f463df..fee8156375 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -32,12 +32,14 @@
#ifdef TOOLS_ENABLED
-#include "../gltf_document.h"
+#include "editor_scene_exporter_gltf_settings.h"
#include "editor/editor_file_system.h"
+#include "editor/editor_inspector.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_file_dialog.h"
-#include "scene/gui/popup_menu.h"
+#include "editor/import/3d/scene_import_settings.h"
+#include "editor/themes/editor_scale.h"
String SceneExporterGLTFPlugin::get_name() const {
return "ConvertGLTF2";
@@ -48,59 +50,72 @@ bool SceneExporterGLTFPlugin::has_main_screen() const {
}
SceneExporterGLTFPlugin::SceneExporterGLTFPlugin() {
- file_export_lib = memnew(EditorFileDialog);
- EditorNode::get_singleton()->get_gui_base()->add_child(file_export_lib);
- file_export_lib->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_gltf2_dialog_action));
- file_export_lib->set_title(TTR("Export Library"));
- file_export_lib->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
- file_export_lib->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
- file_export_lib->clear_filters();
- file_export_lib->add_filter("*.glb");
- file_export_lib->add_filter("*.gltf");
- file_export_lib->set_title(TTR("Export Scene to glTF 2.0 File"));
-
+ _gltf_document.instantiate();
+ // Set up the file dialog.
+ _file_dialog = memnew(EditorFileDialog);
+ _file_dialog->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_export_scene_as_gltf));
+ _file_dialog->set_title(TTR("Export Library"));
+ _file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ _file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ _file_dialog->clear_filters();
+ _file_dialog->add_filter("*.glb");
+ _file_dialog->add_filter("*.gltf");
+ _file_dialog->set_title(TTR("Export Scene to glTF 2.0 File"));
+ EditorNode::get_singleton()->get_gui_base()->add_child(_file_dialog);
+ // Set up the export settings menu.
+ _export_settings.instantiate();
+ _export_settings->generate_property_list(_gltf_document);
+ _settings_inspector = memnew(EditorInspector);
+ _settings_inspector->set_custom_minimum_size(Size2(350, 300) * EDSCALE);
+ _file_dialog->add_side_menu(_settings_inspector, TTR("Export Settings:"));
+ // Add a button to the Scene -> Export menu to pop up the settings dialog.
PopupMenu *menu = get_export_as_menu();
int idx = menu->get_item_count();
menu->add_item(TTR("glTF 2.0 Scene..."));
- menu->set_item_metadata(idx, callable_mp(this, &SceneExporterGLTFPlugin::convert_scene_to_gltf2));
+ menu->set_item_metadata(idx, callable_mp(this, &SceneExporterGLTFPlugin::_popup_gltf_export_dialog));
+}
+
+void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() {
+ Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root();
+ if (!root) {
+ EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
+ return;
+ }
+ // Set the file dialog's file name to the scene name.
+ String filename = String(root->get_scene_file_path().get_file().get_basename());
+ if (filename.is_empty()) {
+ filename = root->get_name();
+ }
+ _file_dialog->set_current_file(filename + String(".gltf"));
+ // Generate and refresh the export settings.
+ _export_settings->generate_property_list(_gltf_document);
+ _settings_inspector->edit(nullptr);
+ _settings_inspector->edit(_export_settings.ptr());
+ // Show the file dialog.
+ _file_dialog->popup_centered_ratio();
}
-void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) {
+void SceneExporterGLTFPlugin::_export_scene_as_gltf(const String &p_file_path) {
Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root();
if (!root) {
EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
return;
}
List<String> deps;
- Ref<GLTFDocument> doc;
- doc.instantiate();
Ref<GLTFState> state;
state.instantiate();
+ state->set_copyright(_export_settings->get_copyright());
int32_t flags = 0;
flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS;
- Error err = doc->append_from_scene(root, state, flags);
+ Error err = _gltf_document->append_from_scene(root, state, flags);
if (err != OK) {
ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err)));
}
- err = doc->write_to_filesystem(state, p_file);
+ err = _gltf_document->write_to_filesystem(state, p_file_path);
if (err != OK) {
ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err)));
}
EditorFileSystem::get_singleton()->scan_changes();
}
-void SceneExporterGLTFPlugin::convert_scene_to_gltf2() {
- Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root();
- if (!root) {
- EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
- return;
- }
- String filename = String(root->get_scene_file_path().get_file().get_basename());
- if (filename.is_empty()) {
- filename = root->get_name();
- }
- file_export_lib->set_current_file(filename + String(".gltf"));
- file_export_lib->popup_centered_ratio();
-}
-
#endif // TOOLS_ENABLED
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
index f92b3c5180..683ff6d4f6 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
@@ -33,18 +33,23 @@
#ifdef TOOLS_ENABLED
-#include "editor_scene_importer_gltf.h"
+#include "../gltf_document.h"
+#include "editor_scene_exporter_gltf_settings.h"
#include "editor/editor_plugin.h"
class EditorFileDialog;
+class EditorInspector;
class SceneExporterGLTFPlugin : public EditorPlugin {
GDCLASS(SceneExporterGLTFPlugin, EditorPlugin);
- EditorFileDialog *file_export_lib = nullptr;
- void _gltf2_dialog_action(String p_file);
- void convert_scene_to_gltf2();
+ Ref<GLTFDocument> _gltf_document;
+ Ref<EditorSceneExporterGLTFSettings> _export_settings;
+ EditorInspector *_settings_inspector = nullptr;
+ EditorFileDialog *_file_dialog = nullptr;
+ void _popup_gltf_export_dialog();
+ void _export_scene_as_gltf(const String &p_file_path);
public:
virtual String get_name() const override;
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
new file mode 100644
index 0000000000..b0283b0c55
--- /dev/null
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
@@ -0,0 +1,176 @@
+/**************************************************************************/
+/* editor_scene_exporter_gltf_settings.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_scene_exporter_gltf_settings.h"
+
+const uint32_t PROP_EDITOR_SCRIPT_VAR = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE;
+
+bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Variant &p_value) {
+ String name_str = String(p_name);
+ if (name_str.contains("/")) {
+ return _set_extension_setting(name_str, p_value);
+ }
+ if (p_name == StringName("image_format")) {
+ _document->set_image_format(p_value);
+ emit_signal("property_list_changed");
+ return true;
+ }
+ if (p_name == StringName("lossy_quality")) {
+ _document->set_lossy_quality(p_value);
+ return true;
+ }
+ if (p_name == StringName("root_node_mode")) {
+ _document->set_root_node_mode((GLTFDocument::RootNodeMode)(int64_t)p_value);
+ return true;
+ }
+ return false;
+}
+
+bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_ret) const {
+ String name_str = String(p_name);
+ if (name_str.contains("/")) {
+ return _get_extension_setting(name_str, r_ret);
+ }
+ if (p_name == StringName("image_format")) {
+ r_ret = _document->get_image_format();
+ return true;
+ }
+ if (p_name == StringName("lossy_quality")) {
+ r_ret = _document->get_lossy_quality();
+ return true;
+ }
+ if (p_name == StringName("root_node_mode")) {
+ r_ret = _document->get_root_node_mode();
+ return true;
+ }
+ return false;
+}
+
+void EditorSceneExporterGLTFSettings::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (PropertyInfo prop : _property_list) {
+ if (prop.name == "lossy_quality") {
+ String image_format = get("image_format");
+ bool is_image_format_lossy = image_format == "JPEG" || image_format.findn("Lossy") != -1;
+ prop.usage = is_image_format_lossy ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(prop);
+ }
+}
+
+bool EditorSceneExporterGLTFSettings::_set_extension_setting(const String &p_name_str, const Variant &p_value) {
+ PackedStringArray split = String(p_name_str).split("/", true, 1);
+ if (!_config_name_to_extension_map.has(split[0])) {
+ return false;
+ }
+ Ref<GLTFDocumentExtension> extension = _config_name_to_extension_map[split[0]];
+ bool valid;
+ extension->set(split[1], p_value, &valid);
+ return valid;
+}
+
+bool EditorSceneExporterGLTFSettings::_get_extension_setting(const String &p_name_str, Variant &r_ret) const {
+ PackedStringArray split = String(p_name_str).split("/", true, 1);
+ if (!_config_name_to_extension_map.has(split[0])) {
+ return false;
+ }
+ Ref<GLTFDocumentExtension> extension = _config_name_to_extension_map[split[0]];
+ bool valid;
+ r_ret = extension->get(split[1], &valid);
+ return valid;
+}
+
+String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
+ String config_prefix = p_extension->get_name();
+ if (!config_prefix.is_empty()) {
+ return config_prefix;
+ }
+ const String class_name = p_extension->get_class_name();
+ config_prefix = class_name.trim_prefix("GLTFDocumentExtension").capitalize();
+ if (!config_prefix.is_empty()) {
+ return config_prefix;
+ }
+ PackedStringArray supported_extensions = p_extension->get_supported_extensions();
+ if (supported_extensions.size() > 0) {
+ return supported_extensions[0];
+ }
+ return "Unknown GLTFDocumentExtension";
+}
+
+// Run this before popping up the export settings, because the extensions may have changed.
+void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document) {
+ _property_list.clear();
+ _document = p_document;
+ String image_format_hint_string = "None,PNG,JPEG";
+ // Add properties from all document extensions.
+ for (Ref<GLTFDocumentExtension> &extension : GLTFDocument::get_all_gltf_document_extensions()) {
+ const String config_prefix = get_friendly_config_prefix(extension);
+ _config_name_to_extension_map[config_prefix] = extension;
+ // If the extension allows saving in different image formats, add to the enum.
+ PackedStringArray saveable_image_formats = extension->get_saveable_image_formats();
+ for (int i = 0; i < saveable_image_formats.size(); i++) {
+ image_format_hint_string += "," + saveable_image_formats[i];
+ }
+ // Look through the extension's properties and find the relevant ones.
+ List<PropertyInfo> ext_prop_list;
+ extension->get_property_list(&ext_prop_list);
+ for (const PropertyInfo &prop : ext_prop_list) {
+ // We only want properties that will show up in the exporter
+ // settings list. Exclude Resource's properties, as they are
+ // not relevant to the exporter. Include any user-defined script
+ // variables exposed to the editor (PROP_EDITOR_SCRIPT_VAR).
+ if ((prop.usage & PROP_EDITOR_SCRIPT_VAR) == PROP_EDITOR_SCRIPT_VAR) {
+ PropertyInfo ext_prop = prop;
+ ext_prop.name = config_prefix + "/" + prop.name;
+ _property_list.push_back(ext_prop);
+ }
+ }
+ }
+ // Add top-level properties (in addition to what _bind_methods registers).
+ PropertyInfo image_format_prop = PropertyInfo(Variant::STRING, "image_format", PROPERTY_HINT_ENUM, image_format_hint_string);
+ _property_list.push_back(image_format_prop);
+ PropertyInfo lossy_quality_prop = PropertyInfo(Variant::FLOAT, "lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01");
+ _property_list.push_back(lossy_quality_prop);
+ PropertyInfo root_node_mode_prop = PropertyInfo(Variant::INT, "root_node_mode", PROPERTY_HINT_ENUM, "Single Root,Keep Root,Multi Root");
+ _property_list.push_back(root_node_mode_prop);
+}
+
+String EditorSceneExporterGLTFSettings::get_copyright() const {
+ return _copyright;
+}
+
+void EditorSceneExporterGLTFSettings::set_copyright(const String &p_copyright) {
+ _copyright = p_copyright;
+}
+
+void EditorSceneExporterGLTFSettings::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_copyright"), &EditorSceneExporterGLTFSettings::get_copyright);
+ ClassDB::bind_method(D_METHOD("set_copyright", "copyright"), &EditorSceneExporterGLTFSettings::set_copyright);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "copyright", PROPERTY_HINT_PLACEHOLDER_TEXT, "Example: 2014 Godette"), "set_copyright", "get_copyright");
+}
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
new file mode 100644
index 0000000000..6032932367
--- /dev/null
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
@@ -0,0 +1,64 @@
+/**************************************************************************/
+/* editor_scene_exporter_gltf_settings.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef EDITOR_SCENE_EXPORTER_GLTF_SETTINGS_H
+#define EDITOR_SCENE_EXPORTER_GLTF_SETTINGS_H
+
+#ifdef TOOLS_ENABLED
+
+#include "../gltf_document.h"
+
+class EditorSceneExporterGLTFSettings : public RefCounted {
+ GDCLASS(EditorSceneExporterGLTFSettings, RefCounted);
+ List<PropertyInfo> _property_list;
+ Ref<GLTFDocument> _document;
+ HashMap<String, Ref<GLTFDocumentExtension>> _config_name_to_extension_map;
+
+ String _copyright;
+
+protected:
+ static void _bind_methods();
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ bool _set_extension_setting(const String &p_name_str, const Variant &p_value);
+ bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const;
+
+public:
+ void generate_property_list(Ref<GLTFDocument> p_document);
+
+ String get_copyright() const;
+ void set_copyright(const String &p_copyright);
+};
+
+#endif // TOOLS_ENABLED
+
+#endif // EDITOR_SCENE_EXPORTER_GLTF_SETTINGS_H
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 9587604e56..a91856c4a1 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -38,10 +38,10 @@
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/themes/editor_scale.h"
#include "main/main.h"
#include "scene/gui/line_edit.h"
@@ -55,20 +55,7 @@
#endif
static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
- String path = p_path;
-#ifdef WINDOWS_ENABLED
- path = path.path_join("blender.exe");
-#else
- path = path.path_join("blender");
-#endif
-
-#if defined(MACOS_ENABLED)
- if (!FileAccess::exists(path)) {
- path = p_path.path_join("Blender");
- }
-#endif
-
- if (!FileAccess::exists(path)) {
+ if (!FileAccess::exists(p_path)) {
if (r_err) {
*r_err = TTR("Path does not contain a Blender installation.");
}
@@ -77,7 +64,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
List<String> args;
args.push_back("--version");
String pipe;
- Error err = OS::get_singleton()->execute(path, args, &pipe);
+ Error err = OS::get_singleton()->execute(p_path, args, &pipe);
if (err != OK) {
if (r_err) {
*r_err = TTR("Can't execute Blender binary.");
@@ -87,7 +74,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
int bl = pipe.find("Blender ");
if (bl == -1) {
if (r_err) {
- *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path);
+ *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path);
}
return false;
}
@@ -126,7 +113,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions)
Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
- String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
if (blender_major_version == -1 || blender_minor_version == -1) {
_get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr);
@@ -134,7 +121,19 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
// Get global paths for source and sink.
// Escape paths to be valid Python strings to embed in the script.
- const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape();
+ String source_global = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+#ifdef WINDOWS_ENABLED
+ // On Windows, when using a network share path, the above will return a path starting with "//"
+ // which once handed to Blender will be treated like a relative path. So we need to replace the
+ // first two characters with "\\" to make it absolute again.
+ if (source_global.is_network_share_path()) {
+ source_global = "\\\\" + source_global.substr(2);
+ }
+#endif
+
+ source_global = source_global.c_escape();
+
const String blend_basename = p_path.get_file().get_basename();
const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join(
vformat("%s-%s.gltf", blend_basename, p_path.md5_text()));
@@ -357,7 +356,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const {
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path").operator String())) {
+ if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender_path").operator String())) {
// Intending to import Blender, but blend not configured.
return true;
}
@@ -397,11 +396,59 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path
}
}
-bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path(String p_path) {
- if (_test_blender_path(p_path)) {
- auto_detected_path = p_path;
- return true;
+bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path() {
+ // Autodetect
+ auto_detected_path = "";
+
+#if defined(MACOS_ENABLED)
+ Vector<String> find_paths = {
+ "/opt/homebrew/bin/blender",
+ "/opt/local/bin/blender",
+ "/usr/local/bin/blender",
+ "/usr/local/opt/blender",
+ "/Applications/Blender.app/Contents/MacOS/Blender",
+ };
+ {
+ List<String> mdfind_args;
+ mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender");
+
+ String output;
+ Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
+ if (err == OK) {
+ for (const String &find_path : output.split("\n")) {
+ find_paths.push_back(find_path.path_join("Contents/MacOS/Blender"));
+ }
+ }
}
+#elif defined(WINDOWS_ENABLED)
+ Vector<String> find_paths = {
+ "C:\\Program Files\\Blender Foundation\\blender.exe",
+ "C:\\Program Files (x86)\\Blender Foundation\\blender.exe",
+ };
+ {
+ char blender_opener_path[MAX_PATH];
+ DWORD path_len = MAX_PATH;
+ HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len);
+ if (res == S_OK) {
+ find_paths.push_back(String(blender_opener_path).get_base_dir().path_join("blender.exe"));
+ }
+ }
+
+#elif defined(UNIX_ENABLED)
+ Vector<String> find_paths = {
+ "/usr/bin/blender",
+ "/usr/local/bin/blender",
+ "/opt/blender/bin/blender",
+ };
+#endif
+
+ for (const String &find_path : find_paths) {
+ if (_test_blender_path(find_path)) {
+ auto_detected_path = find_path;
+ return true;
+ }
+ }
+
return false;
}
@@ -415,7 +462,7 @@ void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_pat
}
void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() {
if (blender_path->get_text() != String()) {
- browse_dialog->set_current_dir(blender_path->get_text());
+ browse_dialog->set_current_file(blender_path->get_text());
}
browse_dialog->popup_centered_ratio();
@@ -467,76 +514,10 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog);
}
- String path = EDITOR_GET("filesystem/import/blender/blender3_path");
-
- if (path == "") {
- // Autodetect
- auto_detected_path = "";
+ String path = EDITOR_GET("filesystem/import/blender/blender_path");
-#if defined(MACOS_ENABLED)
-
- {
- Vector<String> mdfind_paths;
- {
- List<String> mdfind_args;
- mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender");
-
- String output;
- Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
- if (err == OK) {
- mdfind_paths = output.split("\n");
- }
- }
-
- bool found = false;
- for (const String &found_path : mdfind_paths) {
- found = _autodetect_path(found_path.path_join("Contents/MacOS"));
- if (found) {
- break;
- }
- }
- if (!found) {
- found = _autodetect_path("/opt/homebrew/bin");
- }
- if (!found) {
- found = _autodetect_path("/opt/local/bin");
- }
- if (!found) {
- found = _autodetect_path("/usr/local/bin");
- }
- if (!found) {
- found = _autodetect_path("/usr/local/opt");
- }
- if (!found) {
- found = _autodetect_path("/Applications/Blender.app/Contents/MacOS");
- }
- }
-#elif defined(WINDOWS_ENABLED)
- {
- char blender_opener_path[MAX_PATH];
- DWORD path_len = MAX_PATH;
- HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len);
- if (res == S_OK && _autodetect_path(String(blender_opener_path).get_base_dir())) {
- // Good.
- } else if (_autodetect_path("C:\\Program Files\\Blender Foundation")) {
- // Good.
- } else {
- _autodetect_path("C:\\Program Files (x86)\\Blender Foundation");
- }
- }
-
-#elif defined(UNIX_ENABLED)
- if (_autodetect_path("/usr/bin")) {
- // Good.
- } else if (_autodetect_path("/usr/local/bin")) {
- // Good
- } else {
- _autodetect_path("/opt/blender/bin");
- }
-#endif
- if (auto_detected_path != "") {
- path = auto_detected_path;
- }
+ if (path.is_empty() && _autodetect_path()) {
+ path = auto_detected_path;
}
blender_path->set_text(path);
@@ -557,7 +538,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
if (confirmed) {
// Can only confirm a valid path.
- EditorSettings::get_singleton()->set("filesystem/import/blender/blender3_path", blender_path->get_text());
+ EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path->get_text());
EditorSettings::get_singleton()->save();
} else {
// Disable Blender import
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index ec467db457..ed1b19eaf3 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -34,7 +34,7 @@
#ifdef TOOLS_ENABLED
#include "editor/editor_file_system.h"
-#include "editor/import/resource_importer_scene.h"
+#include "editor/import/3d/resource_importer_scene.h"
class Animation;
class Node;
@@ -95,7 +95,7 @@ class EditorFileSystemImportFormatSupportQueryBlend : public EditorFileSystemImp
String auto_detected_path;
void _validate_path(String p_path);
- bool _autodetect_path(String p_path);
+ bool _autodetect_path();
void _path_confirmed();
diff --git a/modules/gltf/editor/editor_scene_importer_fbx.h b/modules/gltf/editor/editor_scene_importer_fbx.h
index cc60830eac..86ee6568c9 100644
--- a/modules/gltf/editor/editor_scene_importer_fbx.h
+++ b/modules/gltf/editor/editor_scene_importer_fbx.h
@@ -35,7 +35,7 @@
#include "editor/editor_file_system.h"
#include "editor/fbx_importer_manager.h"
-#include "editor/import/resource_importer_scene.h"
+#include "editor/import/3d/resource_importer_scene.h"
class Animation;
class Node;
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index 7726c845bf..ec563bf525 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -33,7 +33,7 @@
#ifdef TOOLS_ENABLED
-#include "editor/import/resource_importer_scene.h"
+#include "editor/import/3d/resource_importer_scene.h"
class Animation;
class Node;
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 582bcf466b..9fdd6034a9 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -185,7 +185,6 @@ Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dict
Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
return err;
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index 512b7aba91..761dff725c 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -33,6 +33,8 @@
#include "../gltf_state.h"
+#include "scene/3d/node_3d.h"
+
class GLTFDocumentExtension : public Resource {
GDCLASS(GLTFDocumentExtension, Resource);
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 2ba5123c31..a9033de7ae 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -34,13 +34,26 @@
// Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
- if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body")) {
+ if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) {
return ERR_SKIP;
}
Dictionary state_json = p_state->get_json();
if (state_json.has("extensions")) {
Dictionary state_extensions = state_json["extensions"];
- if (state_extensions.has("OMI_collider")) {
+ if (state_extensions.has("OMI_physics_shape")) {
+ Dictionary omi_physics_shape_ext = state_extensions["OMI_physics_shape"];
+ if (omi_physics_shape_ext.has("shapes")) {
+ Array state_shape_dicts = omi_physics_shape_ext["shapes"];
+ if (state_shape_dicts.size() > 0) {
+ Array state_shapes;
+ for (int i = 0; i < state_shape_dicts.size(); i++) {
+ state_shapes.push_back(GLTFPhysicsShape::from_dictionary(state_shape_dicts[i]));
+ }
+ p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_shapes);
+ }
+ }
+#ifndef DISABLE_DEPRECATED
+ } else if (state_extensions.has("OMI_collider")) {
Dictionary omi_collider_ext = state_extensions["OMI_collider"];
if (omi_collider_ext.has("colliders")) {
Array state_collider_dicts = omi_collider_ext["colliders"];
@@ -49,9 +62,10 @@ Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vec
for (int i = 0; i < state_collider_dicts.size(); i++) {
state_colliders.push_back(GLTFPhysicsShape::from_dictionary(state_collider_dicts[i]));
}
- p_state->set_additional_data("GLTFPhysicsShapes", state_colliders);
+ p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_colliders);
}
}
+#endif // DISABLE_DEPRECATED
}
}
return OK;
@@ -61,49 +75,87 @@ Vector<String> GLTFDocumentExtensionPhysics::get_supported_extensions() {
Vector<String> ret;
ret.push_back("OMI_collider");
ret.push_back("OMI_physics_body");
+ ret.push_back("OMI_physics_shape");
return ret;
}
Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
+#ifndef DISABLE_DEPRECATED
if (p_extensions.has("OMI_collider")) {
Dictionary node_collider_ext = p_extensions["OMI_collider"];
if (node_collider_ext.has("collider")) {
// "collider" is the index of the collider in the state colliders array.
int node_collider_index = node_collider_ext["collider"];
- Array state_colliders = p_state->get_additional_data("GLTFPhysicsShapes");
+ Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]);
} else {
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(p_extensions["OMI_collider"]));
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext));
}
}
+#endif // DISABLE_DEPRECATED
if (p_extensions.has("OMI_physics_body")) {
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"]));
+ Dictionary physics_body_ext = p_extensions["OMI_physics_body"];
+ if (physics_body_ext.has("collider")) {
+ Dictionary node_collider = physics_body_ext["collider"];
+ // "shape" is the index of the shape in the state shapes array.
+ int node_shape_index = node_collider.get("shape", -1);
+ if (node_shape_index != -1) {
+ Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
+ ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]);
+ } else {
+ // If this node is a collider but does not have a collider
+ // shape, then it only serves to combine together shapes.
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundCollider"), true);
+ }
+ }
+ if (physics_body_ext.has("trigger")) {
+ Dictionary node_trigger = physics_body_ext["trigger"];
+ // "shape" is the index of the shape in the state shapes array.
+ int node_shape_index = node_trigger.get("shape", -1);
+ if (node_shape_index != -1) {
+ Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
+ ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]);
+ } else {
+ // If this node is a trigger but does not have a trigger shape,
+ // then it's a trigger body, what Godot calls an Area3D node.
+ Ref<GLTFPhysicsBody> trigger_body;
+ trigger_body.instantiate();
+ trigger_body->set_body_type("trigger");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), trigger_body);
+ }
+ }
+ if (physics_body_ext.has("motion") || physics_body_ext.has("type")) {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(physics_body_ext));
+ }
}
return OK;
}
-void _setup_collider_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_collider) {
- GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index();
- if (collider_mesh_index == -1) {
- return; // No mesh for this collider.
+void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) {
+ GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index();
+ if (shape_mesh_index == -1) {
+ return; // No mesh for this shape.
}
- Ref<ImporterMesh> importer_mesh = p_collider->get_importer_mesh();
+ Ref<ImporterMesh> importer_mesh = p_gltf_shape->get_importer_mesh();
if (importer_mesh.is_valid()) {
return; // The mesh resource is already set up.
}
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes();
- ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
- Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index];
+ ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
+ Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index];
ERR_FAIL_COND(gltf_mesh.is_null());
importer_mesh = gltf_mesh->get_mesh();
ERR_FAIL_COND(importer_mesh.is_null());
- p_collider->set_importer_mesh(importer_mesh);
+ p_gltf_shape->set_importer_mesh(importer_mesh);
}
-CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_collider, Ref<GLTFPhysicsBody> p_physics_body) {
- print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name());
- bool is_trigger = p_collider->get_is_trigger();
+#ifndef DISABLE_DEPRECATED
+CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, Ref<GLTFPhysicsBody> p_physics_body) {
+ print_verbose("glTF: Creating shape with body for: " + p_gltf_node->get_name());
+ bool is_trigger = p_physics_shape->get_is_trigger();
// This method is used for the case where we must generate a parent body.
// This is can happen for multiple reasons. One possibility is that this
// GLTF file is using OMI_collider but not OMI_physics_body, or at least
@@ -113,10 +165,10 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT
if (p_physics_body.is_valid()) {
// This code is run when the physics body is on the same GLTF node.
body = p_physics_body->to_node();
- if (is_trigger != (p_physics_body->get_body_type() == "trigger")) {
+ if (is_trigger && (p_physics_body->get_body_type() != "trigger")) {
// Edge case: If the body's trigger and the collider's trigger
// are in disagreement, we need to create another new body.
- CollisionObject3D *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr);
+ CollisionObject3D *child = _generate_shape_with_body(p_state, p_gltf_node, p_physics_shape, nullptr);
child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid")));
body->add_child(child);
return body;
@@ -126,33 +178,131 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT
} else {
body = memnew(StaticBody3D);
}
- CollisionShape3D *shape = p_collider->to_node();
+ CollisionShape3D *shape = p_physics_shape->to_node();
shape->set_name(p_gltf_node->get_name() + "Shape");
body->add_child(shape);
return body;
}
+#endif // DISABLE_DEPRECATED
+
+CollisionObject3D *_get_ancestor_collision_object(Node *p_scene_parent) {
+ // Note: Despite the name of the method, at the moment this only checks
+ // the direct parent. Only check more later if Godot adds support for it.
+ if (p_scene_parent) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_scene_parent);
+ if (likely(co)) {
+ return co;
+ }
+ }
+ return nullptr;
+}
+
+Node3D *_generate_shape_node_and_body_if_needed(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, CollisionObject3D *p_col_object, bool p_is_trigger) {
+ // If we need to generate a body node, do so.
+ CollisionObject3D *body_node = nullptr;
+ if (p_is_trigger || p_physics_shape->get_is_trigger()) {
+ // If the shape wants to be a trigger but it doesn't
+ // have an Area3D parent, we need to make one.
+ if (!Object::cast_to<Area3D>(p_col_object)) {
+ body_node = memnew(Area3D);
+ }
+ } else {
+ if (!Object::cast_to<PhysicsBody3D>(p_col_object)) {
+ body_node = memnew(StaticBody3D);
+ }
+ }
+ // Generate the shape node.
+ _setup_shape_mesh_resource_from_index_if_needed(p_state, p_physics_shape);
+ CollisionShape3D *shape_node = p_physics_shape->to_node(true);
+ if (body_node) {
+ shape_node->set_name(p_gltf_node->get_name() + "Shape");
+ body_node->add_child(shape_node);
+ return body_node;
+ }
+ return shape_node;
+}
+
+// Either add the child to the parent, or return the child if there is no parent.
+Node3D *_add_physics_node_to_given_node(Node3D *p_current_node, Node3D *p_child, Ref<GLTFNode> p_gltf_node) {
+ if (!p_current_node) {
+ return p_child;
+ }
+ String suffix;
+ if (Object::cast_to<CollisionShape3D>(p_child)) {
+ suffix = "Shape";
+ } else if (Object::cast_to<Area3D>(p_child)) {
+ suffix = "Trigger";
+ } else {
+ suffix = "Collider";
+ }
+ p_child->set_name(p_gltf_node->get_name() + suffix);
+ p_current_node->add_child(p_child);
+ return p_current_node;
+}
Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
- Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
- if (collider.is_valid()) {
- _setup_collider_mesh_resource_from_index_if_needed(p_state, collider);
- // If the collider has the correct type of parent, we just return one node.
- if (collider->get_is_trigger()) {
- if (Object::cast_to<Area3D>(p_scene_parent)) {
- return collider->to_node(true);
+ Ref<GLTFPhysicsBody> gltf_physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
+#ifndef DISABLE_DEPRECATED
+ // This deprecated code handles OMI_collider (which we internally name "GLTFPhysicsShape").
+ Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
+ if (gltf_physics_shape.is_valid()) {
+ _setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape);
+ // If this GLTF node specifies both a shape and a body, generate both.
+ if (gltf_physics_body.is_valid()) {
+ return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body);
+ }
+ CollisionObject3D *ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent);
+ if (gltf_physics_shape->get_is_trigger()) {
+ // If the shape wants to be a trigger and it already has a
+ // trigger parent, we only need to make the shape node.
+ if (Object::cast_to<Area3D>(ancestor_col_obj)) {
+ return gltf_physics_shape->to_node(true);
}
- } else {
- if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) {
- return collider->to_node(true);
+ } else if (ancestor_col_obj != nullptr) {
+ // If the shape has a valid parent, only make the shape node.
+ return gltf_physics_shape->to_node(true);
+ }
+ // Otherwise, we need to create a new body.
+ return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, nullptr);
+ }
+#endif // DISABLE_DEPRECATED
+ Node3D *ret = nullptr;
+ CollisionObject3D *ancestor_col_obj = nullptr;
+ if (gltf_physics_body.is_valid()) {
+ ancestor_col_obj = gltf_physics_body->to_node();
+ ret = ancestor_col_obj;
+ } else {
+ ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent);
+ if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) {
+ if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) {
+ // If the GLTF file wants this node to group solid shapes together,
+ // and there is no parent body, we need to create a static body.
+ ancestor_col_obj = memnew(StaticBody3D);
+ ret = ancestor_col_obj;
}
}
- return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body);
}
- if (physics_body.is_valid()) {
- return physics_body->to_node();
+ // Add the shapes to the tree. When an ancestor body is present, use it.
+ // If an explicit body was specified, it has already been generated and
+ // set above. If there is no ancestor body, we will either generate an
+ // Area3D or StaticBody3D implicitly, so prefer an Area3D as the base
+ // node for best compatibility with signal connections to this node.
+ Ref<GLTFPhysicsShape> gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ Ref<GLTFPhysicsShape> gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ bool is_ancestor_col_obj_solid = Object::cast_to<PhysicsBody3D>(ancestor_col_obj);
+ if (is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
}
- return nullptr;
+ if (gltf_physics_trigger_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_trigger_shape, ancestor_col_obj, true);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
+ }
+ if (!is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
+ }
+ return ret;
}
// Export process.
@@ -202,22 +352,26 @@ GLTFMeshIndex _get_or_insert_mesh_in_state(Ref<GLTFState> p_state, Ref<ImporterM
void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
if (cast_to<CollisionShape3D>(p_scene_node)) {
- CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node);
- Ref<GLTFPhysicsShape> collider = GLTFPhysicsShape::from_node(shape);
+ CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node);
+ Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape);
{
- Ref<ImporterMesh> importer_mesh = collider->get_importer_mesh();
+ Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh();
if (importer_mesh.is_valid()) {
- collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
+ gltf_shape->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
}
}
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), collider);
+ if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node->get_parent()))) {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), gltf_shape);
+ } else {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), gltf_shape);
+ }
} else if (cast_to<CollisionObject3D>(p_scene_node)) {
- CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node);
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body));
+ CollisionObject3D *godot_body = Object::cast_to<CollisionObject3D>(p_scene_node);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(godot_body));
}
}
-Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) {
+Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) {
Dictionary state_json = p_state->get_json();
Dictionary state_extensions;
if (state_json.has("extensions")) {
@@ -225,48 +379,60 @@ Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) {
} else {
state_json["extensions"] = state_extensions;
}
- Dictionary omi_collider_ext;
- if (state_extensions.has("OMI_collider")) {
- omi_collider_ext = state_extensions["OMI_collider"];
+ Dictionary omi_physics_shape_ext;
+ if (state_extensions.has("OMI_physics_shape")) {
+ omi_physics_shape_ext = state_extensions["OMI_physics_shape"];
} else {
- state_extensions["OMI_collider"] = omi_collider_ext;
- p_state->add_used_extension("OMI_collider");
+ state_extensions["OMI_physics_shape"] = omi_physics_shape_ext;
+ p_state->add_used_extension("OMI_physics_shape");
}
- Array state_colliders;
- if (omi_collider_ext.has("colliders")) {
- state_colliders = omi_collider_ext["colliders"];
+ Array state_shapes;
+ if (omi_physics_shape_ext.has("shapes")) {
+ state_shapes = omi_physics_shape_ext["shapes"];
} else {
- omi_collider_ext["colliders"] = state_colliders;
+ omi_physics_shape_ext["shapes"] = state_shapes;
+ }
+ return state_shapes;
+}
+
+Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
+ Array state_shapes = _get_or_create_state_shapes_in_state(p_state);
+ int size = state_shapes.size();
+ Dictionary shape_property;
+ Dictionary shape_dict = p_physics_shape->to_dictionary();
+ for (int i = 0; i < size; i++) {
+ Dictionary other = state_shapes[i];
+ if (other == shape_dict) {
+ // De-duplication: If we already have an identical shape,
+ // set the shape index to the existing one and return.
+ shape_property["shape"] = i;
+ return shape_property;
+ }
}
- return state_colliders;
+ // If we don't have an identical shape, add it to the array.
+ state_shapes.push_back(shape_dict);
+ shape_property["shape"] = size;
+ return shape_property;
}
Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) {
- Dictionary node_extensions = r_node_json["extensions"];
+ Dictionary physics_body_ext;
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
if (physics_body.is_valid()) {
- node_extensions["OMI_physics_body"] = physics_body->to_dictionary();
- p_state->add_used_extension("OMI_physics_body");
+ physics_body_ext = physics_body->to_dictionary();
}
- Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
- if (collider.is_valid()) {
- Array state_colliders = _get_or_create_state_colliders_in_state(p_state);
- int size = state_colliders.size();
- Dictionary omi_collider_ext;
- node_extensions["OMI_collider"] = omi_collider_ext;
- Dictionary collider_dict = collider->to_dictionary();
- for (int i = 0; i < size; i++) {
- Dictionary other = state_colliders[i];
- if (other == collider_dict) {
- // De-duplication: If we already have an identical collider,
- // set the collider index to the existing one and return.
- omi_collider_ext["collider"] = i;
- return OK;
- }
- }
- // If we don't have an identical collider, add it to the array.
- state_colliders.push_back(collider_dict);
- omi_collider_ext["collider"] = size;
+ Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ if (collider_shape.is_valid()) {
+ physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape);
+ }
+ Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ if (trigger_shape.is_valid()) {
+ physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape);
+ }
+ if (!physics_body_ext.is_empty()) {
+ Dictionary node_extensions = r_node_json["extensions"];
+ node_extensions["OMI_physics_body"] = physics_body_ext;
+ p_state->add_used_extension("OMI_physics_body");
}
return OK;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index b80f4348c2..271bb9b332 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -50,22 +50,70 @@ void GLTFPhysicsBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity);
ClassDB::bind_method(D_METHOD("get_center_of_mass"), &GLTFPhysicsBody::get_center_of_mass);
ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &GLTFPhysicsBody::set_center_of_mass);
+ ClassDB::bind_method(D_METHOD("get_inertia_diagonal"), &GLTFPhysicsBody::get_inertia_diagonal);
+ ClassDB::bind_method(D_METHOD("set_inertia_diagonal", "inertia_diagonal"), &GLTFPhysicsBody::set_inertia_diagonal);
+ ClassDB::bind_method(D_METHOD("get_inertia_orientation"), &GLTFPhysicsBody::get_inertia_orientation);
+ ClassDB::bind_method(D_METHOD("set_inertia_orientation", "inertia_orientation"), &GLTFPhysicsBody::set_inertia_orientation);
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("get_inertia_tensor"), &GLTFPhysicsBody::get_inertia_tensor);
ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_inertia_tensor);
+#endif // DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass"), "set_center_of_mass", "get_center_of_mass");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia_diagonal"), "set_inertia_diagonal", "get_inertia_diagonal");
+ ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "inertia_orientation"), "set_inertia_orientation", "get_inertia_orientation");
+#ifndef DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor");
+#endif // DISABLE_DEPRECATED
}
String GLTFPhysicsBody::get_body_type() const {
- return body_type;
+ switch (body_type) {
+ case PhysicsBodyType::STATIC:
+ return "static";
+ case PhysicsBodyType::ANIMATABLE:
+ return "animatable";
+ case PhysicsBodyType::CHARACTER:
+ return "character";
+ case PhysicsBodyType::RIGID:
+ return "rigid";
+ case PhysicsBodyType::VEHICLE:
+ return "vehicle";
+ case PhysicsBodyType::TRIGGER:
+ return "trigger";
+ }
+ // Unreachable, the switch cases handle all values the enum can take.
+ // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB.
+ return "rigid";
}
void GLTFPhysicsBody::set_body_type(String p_body_type) {
+ if (p_body_type == "static") {
+ body_type = PhysicsBodyType::STATIC;
+ } else if (p_body_type == "animatable") {
+ body_type = PhysicsBodyType::ANIMATABLE;
+ } else if (p_body_type == "character") {
+ body_type = PhysicsBodyType::CHARACTER;
+ } else if (p_body_type == "rigid") {
+ body_type = PhysicsBodyType::RIGID;
+ } else if (p_body_type == "vehicle") {
+ body_type = PhysicsBodyType::VEHICLE;
+ } else if (p_body_type == "trigger") {
+ body_type = PhysicsBodyType::TRIGGER;
+ } else {
+ ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\".");
+ }
+}
+
+GLTFPhysicsBody::PhysicsBodyType GLTFPhysicsBody::get_physics_body_type() const {
+ return body_type;
+}
+
+void GLTFPhysicsBody::set_physics_body_type(PhysicsBodyType p_body_type) {
body_type = p_body_type;
}
@@ -101,140 +149,215 @@ void GLTFPhysicsBody::set_center_of_mass(const Vector3 &p_center_of_mass) {
center_of_mass = p_center_of_mass;
}
+Vector3 GLTFPhysicsBody::get_inertia_diagonal() const {
+ return inertia_diagonal;
+}
+
+void GLTFPhysicsBody::set_inertia_diagonal(const Vector3 &p_inertia_diagonal) {
+ inertia_diagonal = p_inertia_diagonal;
+}
+
+Quaternion GLTFPhysicsBody::get_inertia_orientation() const {
+ return inertia_orientation;
+}
+
+void GLTFPhysicsBody::set_inertia_orientation(const Quaternion &p_inertia_orientation) {
+ inertia_orientation = p_inertia_orientation;
+}
+
+#ifndef DISABLE_DEPRECATED
Basis GLTFPhysicsBody::get_inertia_tensor() const {
- return inertia_tensor;
+ return Basis::from_scale(inertia_diagonal);
}
void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) {
- inertia_tensor = p_inertia_tensor;
+ inertia_diagonal = p_inertia_tensor.get_main_diagonal();
}
+#endif // DISABLE_DEPRECATED
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
ERR_FAIL_NULL_V_MSG(p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null.");
if (cast_to<CharacterBody3D>(p_body_node)) {
- physics_body->body_type = "character";
+ physics_body->body_type = PhysicsBodyType::CHARACTER;
} else if (cast_to<AnimatableBody3D>(p_body_node)) {
- physics_body->body_type = "kinematic";
+ physics_body->body_type = PhysicsBodyType::ANIMATABLE;
} else if (cast_to<RigidBody3D>(p_body_node)) {
const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node);
physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity();
physics_body->center_of_mass = body->get_center_of_mass();
- Vector3 inertia_diagonal = body->get_inertia();
- physics_body->inertia_tensor = Basis(inertia_diagonal.x, 0, 0, 0, inertia_diagonal.y, 0, 0, 0, inertia_diagonal.z);
+ physics_body->inertia_diagonal = body->get_inertia();
if (body->get_center_of_mass() != Vector3()) {
WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to GLTF.");
}
if (cast_to<VehicleBody3D>(p_body_node)) {
- physics_body->body_type = "vehicle";
+ physics_body->body_type = PhysicsBodyType::VEHICLE;
} else {
- physics_body->body_type = "rigid";
+ physics_body->body_type = PhysicsBodyType::RIGID;
}
} else if (cast_to<StaticBody3D>(p_body_node)) {
- physics_body->body_type = "static";
+ physics_body->body_type = PhysicsBodyType::STATIC;
} else if (cast_to<Area3D>(p_body_node)) {
- physics_body->body_type = "trigger";
+ physics_body->body_type = PhysicsBodyType::TRIGGER;
}
return physics_body;
}
CollisionObject3D *GLTFPhysicsBody::to_node() const {
- if (body_type == "character") {
- CharacterBody3D *body = memnew(CharacterBody3D);
- return body;
- }
- if (body_type == "kinematic") {
- AnimatableBody3D *body = memnew(AnimatableBody3D);
- return body;
- }
- if (body_type == "vehicle") {
- VehicleBody3D *body = memnew(VehicleBody3D);
- body->set_mass(mass);
- body->set_linear_velocity(linear_velocity);
- body->set_angular_velocity(angular_velocity);
- body->set_inertia(inertia_tensor.get_main_diagonal());
- body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
- body->set_center_of_mass(center_of_mass);
- return body;
- }
- if (body_type == "rigid") {
- RigidBody3D *body = memnew(RigidBody3D);
- body->set_mass(mass);
- body->set_linear_velocity(linear_velocity);
- body->set_angular_velocity(angular_velocity);
- body->set_inertia(inertia_tensor.get_main_diagonal());
- body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
- body->set_center_of_mass(center_of_mass);
- return body;
- }
- if (body_type == "static") {
- StaticBody3D *body = memnew(StaticBody3D);
- return body;
- }
- if (body_type == "trigger") {
- Area3D *body = memnew(Area3D);
- return body;
+ switch (body_type) {
+ case PhysicsBodyType::CHARACTER: {
+ CharacterBody3D *body = memnew(CharacterBody3D);
+ return body;
+ }
+ case PhysicsBodyType::ANIMATABLE: {
+ AnimatableBody3D *body = memnew(AnimatableBody3D);
+ return body;
+ }
+ case PhysicsBodyType::VEHICLE: {
+ VehicleBody3D *body = memnew(VehicleBody3D);
+ body->set_mass(mass);
+ body->set_linear_velocity(linear_velocity);
+ body->set_angular_velocity(angular_velocity);
+ body->set_inertia(inertia_diagonal);
+ body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
+ return body;
+ }
+ case PhysicsBodyType::RIGID: {
+ RigidBody3D *body = memnew(RigidBody3D);
+ body->set_mass(mass);
+ body->set_linear_velocity(linear_velocity);
+ body->set_angular_velocity(angular_velocity);
+ body->set_inertia(inertia_diagonal);
+ body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
+ return body;
+ }
+ case PhysicsBodyType::STATIC: {
+ StaticBody3D *body = memnew(StaticBody3D);
+ return body;
+ }
+ case PhysicsBodyType::TRIGGER: {
+ Area3D *body = memnew(Area3D);
+ return body;
+ }
}
- ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown.");
+ // Unreachable, the switch cases handle all values the enum can take.
+ // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB.
+ return nullptr;
}
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
- ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'.");
- const String &body_type = p_dictionary["type"];
- physics_body->body_type = body_type;
-
- if (p_dictionary.has("mass")) {
- physics_body->mass = p_dictionary["mass"];
+ Dictionary motion;
+ if (p_dictionary.has("motion")) {
+ motion = p_dictionary["motion"];
+#ifndef DISABLE_DEPRECATED
+ } else {
+ motion = p_dictionary;
+#endif // DISABLE_DEPRECATED
}
- if (p_dictionary.has("linearVelocity")) {
- const Array &arr = p_dictionary["linearVelocity"];
+ if (motion.has("type")) {
+ // Read the body type. This representation sits between glTF's and Godot's physics nodes.
+ // While we may only read "static", "kinematic", or "dynamic" from a valid glTF file, we
+ // want to allow another extension to override this to another Godot node type mid-import.
+ // For example, a vehicle extension may want to override the body type to "vehicle"
+ // so Godot generates a VehicleBody3D node. Therefore we distinguish by importing
+ // "dynamic" as "rigid", and "kinematic" as "animatable", in the GLTFPhysicsBody code.
+ String body_type_string = motion["type"];
+ if (body_type_string == "static") {
+ physics_body->body_type = PhysicsBodyType::STATIC;
+ } else if (body_type_string == "kinematic") {
+ physics_body->body_type = PhysicsBodyType::ANIMATABLE;
+ } else if (body_type_string == "dynamic") {
+ physics_body->body_type = PhysicsBodyType::RIGID;
+#ifndef DISABLE_DEPRECATED
+ } else if (body_type_string == "character") {
+ physics_body->body_type = PhysicsBodyType::CHARACTER;
+ } else if (body_type_string == "rigid") {
+ physics_body->body_type = PhysicsBodyType::RIGID;
+ } else if (body_type_string == "vehicle") {
+ physics_body->body_type = PhysicsBodyType::VEHICLE;
+ } else if (body_type_string == "trigger") {
+ physics_body->body_type = PhysicsBodyType::TRIGGER;
+#endif // DISABLE_DEPRECATED
+ } else {
+ ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized.");
+ }
+ }
+ if (motion.has("mass")) {
+ physics_body->mass = motion["mass"];
+ }
+ if (motion.has("linearVelocity")) {
+ const Array &arr = motion["linearVelocity"];
if (arr.size() == 3) {
physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("angularVelocity")) {
- const Array &arr = p_dictionary["angularVelocity"];
+ if (motion.has("angularVelocity")) {
+ const Array &arr = motion["angularVelocity"];
if (arr.size() == 3) {
physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("centerOfMass")) {
- const Array &arr = p_dictionary["centerOfMass"];
+ if (motion.has("centerOfMass")) {
+ const Array &arr = motion["centerOfMass"];
if (arr.size() == 3) {
physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("inertiaTensor")) {
- const Array &arr = p_dictionary["inertiaTensor"];
- if (arr.size() == 9) {
- // Only use the diagonal elements of the inertia tensor matrix (principal axes).
- physics_body->set_inertia_tensor(Basis(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8]));
+ if (motion.has("inertiaDiagonal")) {
+ const Array &arr = motion["inertiaDiagonal"];
+ if (arr.size() == 3) {
+ physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The inertia tensor must be a 3x3 matrix (9 number array).");
+ ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers.");
}
}
- if (body_type != "character" && body_type != "kinematic" && body_type != "rigid" && body_type != "static" && body_type != "trigger" && body_type != "vehicle") {
- ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown.");
+ if (motion.has("inertiaOrientation")) {
+ const Array &arr = motion["inertiaOrientation"];
+ if (arr.size() == 4) {
+ physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3]));
+ } else {
+ ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers.");
+ }
}
return physics_body;
}
Dictionary GLTFPhysicsBody::to_dictionary() const {
- Dictionary d;
- d["type"] = body_type;
+ Dictionary ret;
+ if (body_type == PhysicsBodyType::TRIGGER) {
+ // The equivalent of a Godot Area3D node in glTF is a node that
+ // defines that it is a trigger, but does not have a shape.
+ Dictionary trigger;
+ ret["trigger"] = trigger;
+ return ret;
+ }
+ // All non-trigger body types are defined using the motion property.
+ Dictionary motion;
+ // When stored in memory, the body type can correspond to a Godot
+ // node type. However, when exporting to glTF, we need to squash
+ // this down to one of "static", "kinematic", or "dynamic".
+ if (body_type == PhysicsBodyType::STATIC) {
+ motion["type"] = "static";
+ } else if (body_type == PhysicsBodyType::ANIMATABLE || body_type == PhysicsBodyType::CHARACTER) {
+ motion["type"] = "kinematic";
+ } else {
+ motion["type"] = "dynamic";
+ }
if (mass != 1.0) {
- d["mass"] = mass;
+ motion["mass"] = mass;
}
if (linear_velocity != Vector3()) {
Array velocity_array;
@@ -242,7 +365,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = linear_velocity.x;
velocity_array[1] = linear_velocity.y;
velocity_array[2] = linear_velocity.z;
- d["linearVelocity"] = velocity_array;
+ motion["linearVelocity"] = velocity_array;
}
if (angular_velocity != Vector3()) {
Array velocity_array;
@@ -250,7 +373,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = angular_velocity.x;
velocity_array[1] = angular_velocity.y;
velocity_array[2] = angular_velocity.z;
- d["angularVelocity"] = velocity_array;
+ motion["angularVelocity"] = velocity_array;
}
if (center_of_mass != Vector3()) {
Array center_of_mass_array;
@@ -258,22 +381,25 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
center_of_mass_array[0] = center_of_mass.x;
center_of_mass_array[1] = center_of_mass.y;
center_of_mass_array[2] = center_of_mass.z;
- d["centerOfMass"] = center_of_mass_array;
+ motion["centerOfMass"] = center_of_mass_array;
+ }
+ if (inertia_diagonal != Vector3()) {
+ Array inertia_array;
+ inertia_array.resize(3);
+ inertia_array[0] = inertia_diagonal[0];
+ inertia_array[1] = inertia_diagonal[1];
+ inertia_array[2] = inertia_diagonal[2];
+ motion["inertiaDiagonal"] = inertia_array;
}
- if (inertia_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) {
+ if (inertia_orientation != Quaternion()) {
Array inertia_array;
- inertia_array.resize(9);
- inertia_array.fill(0.0);
- inertia_array[0] = inertia_tensor[0][0];
- inertia_array[1] = inertia_tensor[0][1];
- inertia_array[2] = inertia_tensor[0][2];
- inertia_array[3] = inertia_tensor[1][0];
- inertia_array[4] = inertia_tensor[1][1];
- inertia_array[5] = inertia_tensor[1][2];
- inertia_array[6] = inertia_tensor[2][0];
- inertia_array[7] = inertia_tensor[2][1];
- inertia_array[8] = inertia_tensor[2][2];
- d["inertiaTensor"] = inertia_array;
+ inertia_array.resize(4);
+ inertia_array[0] = inertia_orientation[0];
+ inertia_array[1] = inertia_orientation[1];
+ inertia_array[2] = inertia_orientation[2];
+ inertia_array[3] = inertia_orientation[3];
+ motion["inertiaDiagonal"] = inertia_array;
}
- return d;
+ ret["motion"] = motion;
+ return ret;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h
index 391b4b873f..6b21639a7b 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.h
+++ b/modules/gltf/extensions/physics/gltf_physics_body.h
@@ -33,27 +33,47 @@
#include "scene/3d/physics_body_3d.h"
-// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes.
+// GLTFPhysicsBody is an intermediary between Godot's physics body nodes
+// and the OMI_physics_body extension.
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body
class GLTFPhysicsBody : public Resource {
GDCLASS(GLTFPhysicsBody, Resource)
+public:
+ // These values map to Godot's physics body types.
+ // When importing, the body type will be set to the closest match, and
+ // user code can change this to make Godot generate a different node type.
+ // When exporting, this will be squashed down to one of "static",
+ // "kinematic", or "dynamic" motion types, or the "trigger" property.
+ enum class PhysicsBodyType {
+ STATIC,
+ ANIMATABLE,
+ CHARACTER,
+ RIGID,
+ VEHICLE,
+ TRIGGER,
+ };
+
protected:
static void _bind_methods();
private:
- String body_type = "static";
+ PhysicsBodyType body_type = PhysicsBodyType::RIGID;
real_t mass = 1.0;
Vector3 linear_velocity;
Vector3 angular_velocity;
Vector3 center_of_mass;
- Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ Vector3 inertia_diagonal;
+ Quaternion inertia_orientation;
public:
String get_body_type() const;
void set_body_type(String p_body_type);
+ PhysicsBodyType get_physics_body_type() const;
+ void set_physics_body_type(PhysicsBodyType p_body_type);
+
real_t get_mass() const;
void set_mass(real_t p_mass);
@@ -66,8 +86,16 @@ public:
Vector3 get_center_of_mass() const;
void set_center_of_mass(const Vector3 &p_center_of_mass);
+ Vector3 get_inertia_diagonal() const;
+ void set_inertia_diagonal(const Vector3 &p_inertia_diagonal);
+
+ Quaternion get_inertia_orientation() const;
+ void set_inertia_orientation(const Quaternion &p_inertia_orientation);
+
+#ifndef DISABLE_DEPRECATED
Basis get_inertia_tensor() const;
void set_inertia_tensor(Basis p_inertia_tensor);
+#endif // DISABLE_DEPRECATED
static Ref<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node);
CollisionObject3D *to_node() const;
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
index d3c56c0da9..af4ac10313 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
@@ -129,16 +129,16 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
importer_mesh = p_importer_mesh;
}
-Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_collider_node) {
+Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_godot_shape_node) {
Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate();
- ERR_FAIL_NULL_V_MSG(p_collider_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null.");
- Node *parent = p_collider_node->get_parent();
+ ERR_FAIL_NULL_V_MSG(p_godot_shape_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null.");
+ Node *parent = p_godot_shape_node->get_parent();
if (cast_to<const Area3D>(parent)) {
gltf_shape->set_is_trigger(true);
}
// All the code for working with the shape is below this comment.
- Ref<Shape3D> shape_resource = p_collider_node->get_shape();
+ Ref<Shape3D> shape_resource = p_godot_shape_node->get_shape();
ERR_FAIL_COND_V_MSG(shape_resource.is_null(), gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node had a null shape.");
gltf_shape->_shape_cache = shape_resource;
if (cast_to<BoxShape3D>(shape_resource.ptr())) {
@@ -160,7 +160,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll
Ref<SphereShape3D> sphere = shape_resource;
gltf_shape->set_radius(sphere->get_radius());
} else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) {
- gltf_shape->shape_type = "hull";
+ gltf_shape->shape_type = "convex";
Ref<ConvexPolygonShape3D> convex = shape_resource;
Vector<Vector3> hull_points = convex->get_points();
ERR_FAIL_COND_V_MSG(hull_points.size() < 3, gltf_shape, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls.");
@@ -206,7 +206,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll
}
CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
- CollisionShape3D *gltf_shape = memnew(CollisionShape3D);
+ CollisionShape3D *godot_shape_node = memnew(CollisionShape3D);
if (!p_cache_shapes || _shape_cache == nullptr) {
if (shape_type == "box") {
Ref<BoxShape3D> box;
@@ -230,80 +230,88 @@ CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
sphere.instantiate();
sphere->set_radius(radius);
_shape_cache = sphere;
- } else if (shape_type == "hull") {
- ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null.");
+ } else if (shape_type == "convex") {
+ ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null.");
Ref<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape();
_shape_cache = convex;
} else if (shape_type == "trimesh") {
- ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null.");
+ ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null.");
Ref<ConcavePolygonShape3D> concave = importer_mesh->create_trimesh_shape();
_shape_cache = concave;
} else {
ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown.");
}
}
- gltf_shape->set_shape(_shape_cache);
- return gltf_shape;
+ godot_shape_node->set_shape(_shape_cache);
+ return godot_shape_node;
}
Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_dictionary(const Dictionary p_dictionary) {
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFPhysicsShape>(), "Failed to parse GLTFPhysicsShape, missing required field 'type'.");
Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate();
- const String &shape_type = p_dictionary["type"];
+ String shape_type = p_dictionary["type"];
+ if (shape_type == "hull") {
+ shape_type = "convex";
+ }
gltf_shape->shape_type = shape_type;
- if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") {
- ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, hull, and trimesh are supported.");
+ if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "convex" && shape_type != "trimesh") {
+ ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, convex, and trimesh are supported.");
+ }
+ Dictionary properties;
+ if (p_dictionary.has(shape_type)) {
+ properties = p_dictionary[shape_type];
+ } else {
+ properties = p_dictionary;
}
- if (p_dictionary.has("radius")) {
- gltf_shape->set_radius(p_dictionary["radius"]);
+ if (properties.has("radius")) {
+ gltf_shape->set_radius(properties["radius"]);
}
- if (p_dictionary.has("height")) {
- gltf_shape->set_height(p_dictionary["height"]);
+ if (properties.has("height")) {
+ gltf_shape->set_height(properties["height"]);
}
- if (p_dictionary.has("size")) {
- const Array &arr = p_dictionary["size"];
+ if (properties.has("size")) {
+ const Array &arr = properties["size"];
if (arr.size() == 3) {
gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("isTrigger")) {
- gltf_shape->set_is_trigger(p_dictionary["isTrigger"]);
+ if (properties.has("isTrigger")) {
+ gltf_shape->set_is_trigger(properties["isTrigger"]);
}
- if (p_dictionary.has("mesh")) {
- gltf_shape->set_mesh_index(p_dictionary["mesh"]);
+ if (properties.has("mesh")) {
+ gltf_shape->set_mesh_index(properties["mesh"]);
}
- if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) {
+ if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "convex" || shape_type == "trimesh"))) {
ERR_PRINT("Error parsing GLTFPhysicsShape: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index.");
}
return gltf_shape;
}
Dictionary GLTFPhysicsShape::to_dictionary() const {
- Dictionary d;
- d["type"] = shape_type;
+ Dictionary gltf_shape;
+ gltf_shape["type"] = shape_type;
+ Dictionary sub;
if (shape_type == "box") {
Array size_array;
size_array.resize(3);
size_array[0] = size.x;
size_array[1] = size.y;
size_array[2] = size.z;
- d["size"] = size_array;
+ sub["size"] = size_array;
} else if (shape_type == "capsule") {
- d["radius"] = get_radius();
- d["height"] = get_height();
+ sub["radius"] = get_radius();
+ sub["height"] = get_height();
} else if (shape_type == "cylinder") {
- d["radius"] = get_radius();
- d["height"] = get_height();
+ sub["radius"] = get_radius();
+ sub["height"] = get_height();
} else if (shape_type == "sphere") {
- d["radius"] = get_radius();
- } else if (shape_type == "trimesh" || shape_type == "hull") {
- d["mesh"] = get_mesh_index();
- }
- if (is_trigger) {
- d["isTrigger"] = is_trigger;
+ sub["radius"] = get_radius();
+ } else if (shape_type == "trimesh" || shape_type == "convex") {
+ sub["mesh"] = get_mesh_index();
}
- return d;
+ gltf_shape[shape_type] = sub;
+ return gltf_shape;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.h b/modules/gltf/extensions/physics/gltf_physics_shape.h
index efecf27e1b..4f7ac39292 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.h
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.h
@@ -37,8 +37,9 @@
class ImporterMesh;
-// GLTFPhysicsShape is an intermediary between OMI_collider and Godot's collision shape nodes.
-// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider
+// GLTFPhysicsShape is an intermediary between Godot's collision shape nodes
+// and the OMI_physics_shape extension.
+// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape
class GLTFPhysicsShape : public Resource {
GDCLASS(GLTFPhysicsShape, Resource)
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 4060f7f626..ff2f290e4f 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -250,7 +250,7 @@ Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
}
Error GLTFDocument::_serialize_scenes(Ref<GLTFState> p_state) {
- ERR_FAIL_COND_V_MSG(p_state->root_nodes.size() == 0, ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
+ ERR_FAIL_COND_V_MSG(p_state->root_nodes.is_empty(), ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
// Godot only supports one scene per glTF file.
Array scenes;
Dictionary scene_dict;
@@ -405,6 +405,7 @@ static Vector<real_t> _xform_to_array(const Transform3D p_transform) {
Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
Array nodes;
+ const int scene_node_count = p_state->scene_nodes.size();
for (int i = 0; i < p_state->nodes.size(); i++) {
Dictionary node;
Ref<GLTFNode> gltf_node = p_state->nodes[i];
@@ -429,20 +430,22 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
}
if (gltf_node->skeleton != -1 && gltf_node->skin < 0) {
}
- if (gltf_node->xform != Transform3D()) {
- node["matrix"] = _xform_to_array(gltf_node->xform);
- }
-
- if (!gltf_node->rotation.is_equal_approx(Quaternion())) {
- node["rotation"] = _quaternion_to_array(gltf_node->rotation);
- }
-
- if (!gltf_node->scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) {
- node["scale"] = _vec3_to_arr(gltf_node->scale);
- }
-
- if (!gltf_node->position.is_zero_approx()) {
- node["translation"] = _vec3_to_arr(gltf_node->position);
+ if (gltf_node->transform.basis.is_orthogonal()) {
+ // An orthogonal transform is decomposable into TRS, so prefer that.
+ const Vector3 position = gltf_node->get_position();
+ if (!position.is_zero_approx()) {
+ node["translation"] = _vec3_to_arr(position);
+ }
+ const Quaternion rotation = gltf_node->get_rotation();
+ if (!rotation.is_equal_approx(Quaternion())) {
+ node["rotation"] = _quaternion_to_array(rotation);
+ }
+ const Vector3 scale = gltf_node->get_scale();
+ if (!scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) {
+ node["scale"] = _vec3_to_arr(scale);
+ }
+ } else {
+ node["matrix"] = _xform_to_array(gltf_node->transform);
}
if (gltf_node->children.size()) {
Array children;
@@ -452,10 +455,13 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
node["children"] = children;
}
+ Node *scene_node = nullptr;
+ if (i < scene_node_count) {
+ scene_node = p_state->scene_nodes[i];
+ }
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- ERR_CONTINUE(!p_state->scene_nodes.find(i));
- Error err = ext->export_node(p_state, gltf_node, node, p_state->scene_nodes[i]);
+ Error err = ext->export_node(p_state, gltf_node, node, scene_node);
ERR_CONTINUE(err != OK);
}
@@ -605,20 +611,17 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
node->skin = n["skin"];
}
if (n.has("matrix")) {
- node->xform = _arr_to_xform(n["matrix"]);
+ node->transform = _arr_to_xform(n["matrix"]);
} else {
if (n.has("translation")) {
- node->position = _arr_to_vec3(n["translation"]);
+ node->set_position(_arr_to_vec3(n["translation"]));
}
if (n.has("rotation")) {
- node->rotation = _arr_to_quaternion(n["rotation"]);
+ node->set_rotation(_arr_to_quaternion(n["rotation"]));
}
if (n.has("scale")) {
- node->scale = _arr_to_vec3(n["scale"]);
+ node->set_scale(_arr_to_vec3(n["scale"]));
}
-
- node->xform.basis.set_quaternion_scale(node->rotation, node->scale);
- node->xform.origin = node->position;
}
if (n.has("extensions")) {
@@ -803,7 +806,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
buffer_data = FileAccess::get_file_as_bytes(uri);
- ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
+ ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
}
ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR);
@@ -1541,7 +1544,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
}
}
- ERR_FAIL_COND_V(attribs.size() == 0, -1);
+ ERR_FAIL_COND_V(attribs.is_empty(), -1);
Ref<GLTFAccessor> accessor;
accessor.instantiate();
@@ -1900,7 +1903,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
_calc_accessor_min_max(i, element_count, type_max, attribs, type_min);
}
- ERR_FAIL_COND_V(!attribs.size(), -1);
+ ERR_FAIL_COND_V(attribs.is_empty(), -1);
Ref<GLTFAccessor> accessor;
accessor.instantiate();
@@ -2218,7 +2221,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Dictionary attributes;
{
Vector<Vector3> a = array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(a.is_empty(), ERR_INVALID_DATA);
attributes["POSITION"] = _encode_accessor_as_vec3(p_state, a, true);
vertex_num = a.size();
}
@@ -2785,7 +2788,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
} else if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
//generate indices because they need to be swapped for CW/CCW
const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(vertices.is_empty(), ERR_PARSE_ERROR);
Vector<int> indices;
const int vs = vertices.size();
indices.resize(vs);
@@ -2916,7 +2919,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
if (t.has("TANGENT")) {
const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(p_state, t["TANGENT"], true);
const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT];
- ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(src_tangents.is_empty(), ERR_PARSE_ERROR);
Vector<float> tangents_v4;
@@ -3218,7 +3221,7 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
bool must_import = true;
Vector<uint8_t> img_data = p_image->get_data();
Dictionary generator_parameters;
- String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name();
+ String file_path = p_state->get_base_path().path_join(p_state->filename.get_basename() + "_" + p_image->get_name());
file_path += p_file_extension.is_empty() ? ".png" : p_file_extension;
if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
@@ -3230,6 +3233,8 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
if (!generator_parameters.has("md5")) {
must_import = false; // Didn't come from a gltf document; don't overwrite.
}
+ }
+ if (must_import) {
String existing_md5 = generator_parameters["md5"];
unsigned char md5_hash[16];
CryptoCore::md5(img_data.ptr(), img_data.size(), md5_hash);
@@ -3658,141 +3663,143 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
mr["metallicFactor"] = base_material->get_metallic();
mr["roughnessFactor"] = base_material->get_roughness();
- bool has_roughness = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid();
- bool has_ao = base_material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid();
- bool has_metalness = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid();
- if (has_ao || has_roughness || has_metalness) {
- Dictionary mrt;
- Ref<Texture2D> roughness_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS);
- BaseMaterial3D::TextureChannel roughness_channel = base_material->get_roughness_texture_channel();
- Ref<Texture2D> metallic_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC);
- BaseMaterial3D::TextureChannel metalness_channel = base_material->get_metallic_texture_channel();
- Ref<Texture2D> ao_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION);
- BaseMaterial3D::TextureChannel ao_channel = base_material->get_ao_texture_channel();
- Ref<ImageTexture> orm_texture;
- orm_texture.instantiate();
- Ref<Image> orm_image;
- orm_image.instantiate();
- int32_t height = 0;
- int32_t width = 0;
- Ref<Image> ao_image;
- if (has_ao) {
- height = ao_texture->get_height();
- width = ao_texture->get_width();
- ao_image = ao_texture->get_image();
- Ref<ImageTexture> img_tex = ao_image;
- if (img_tex.is_valid()) {
- ao_image = img_tex->get_image();
- }
- if (ao_image->is_compressed()) {
- ao_image->decompress();
- }
- }
- Ref<Image> roughness_image;
- if (has_roughness) {
- height = roughness_texture->get_height();
- width = roughness_texture->get_width();
- roughness_image = roughness_texture->get_image();
- Ref<ImageTexture> img_tex = roughness_image;
- if (img_tex.is_valid()) {
- roughness_image = img_tex->get_image();
- }
- if (roughness_image->is_compressed()) {
- roughness_image->decompress();
- }
- }
- Ref<Image> metallness_image;
- if (has_metalness) {
- height = metallic_texture->get_height();
- width = metallic_texture->get_width();
- metallness_image = metallic_texture->get_image();
- Ref<ImageTexture> img_tex = metallness_image;
- if (img_tex.is_valid()) {
- metallness_image = img_tex->get_image();
- }
- if (metallness_image->is_compressed()) {
- metallness_image->decompress();
+ if (_image_format != "None") {
+ bool has_roughness = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid();
+ bool has_ao = base_material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid();
+ bool has_metalness = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid();
+ if (has_ao || has_roughness || has_metalness) {
+ Dictionary mrt;
+ Ref<Texture2D> roughness_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS);
+ BaseMaterial3D::TextureChannel roughness_channel = base_material->get_roughness_texture_channel();
+ Ref<Texture2D> metallic_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC);
+ BaseMaterial3D::TextureChannel metalness_channel = base_material->get_metallic_texture_channel();
+ Ref<Texture2D> ao_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION);
+ BaseMaterial3D::TextureChannel ao_channel = base_material->get_ao_texture_channel();
+ Ref<ImageTexture> orm_texture;
+ orm_texture.instantiate();
+ Ref<Image> orm_image;
+ orm_image.instantiate();
+ int32_t height = 0;
+ int32_t width = 0;
+ Ref<Image> ao_image;
+ if (has_ao) {
+ height = ao_texture->get_height();
+ width = ao_texture->get_width();
+ ao_image = ao_texture->get_image();
+ Ref<ImageTexture> img_tex = ao_image;
+ if (img_tex.is_valid()) {
+ ao_image = img_tex->get_image();
+ }
+ if (ao_image->is_compressed()) {
+ ao_image->decompress();
+ }
}
- }
- Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
- if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
- height = albedo_texture->get_height();
- width = albedo_texture->get_width();
- }
- orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8);
- if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) {
- ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) {
- roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) {
- metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- for (int32_t h = 0; h < height; h++) {
- for (int32_t w = 0; w < width; w++) {
- Color c = Color(1.0f, 1.0f, 1.0f);
- if (has_ao) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) {
- c.r = ao_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) {
- c.r = ao_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) {
- c.r = ao_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) {
- c.r = ao_image->get_pixel(w, h).a;
- }
+ Ref<Image> roughness_image;
+ if (has_roughness) {
+ height = roughness_texture->get_height();
+ width = roughness_texture->get_width();
+ roughness_image = roughness_texture->get_image();
+ Ref<ImageTexture> img_tex = roughness_image;
+ if (img_tex.is_valid()) {
+ roughness_image = img_tex->get_image();
}
- if (has_roughness) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).a;
- }
+ if (roughness_image->is_compressed()) {
+ roughness_image->decompress();
+ }
+ }
+ Ref<Image> metallness_image;
+ if (has_metalness) {
+ height = metallic_texture->get_height();
+ width = metallic_texture->get_width();
+ metallness_image = metallic_texture->get_image();
+ Ref<ImageTexture> img_tex = metallness_image;
+ if (img_tex.is_valid()) {
+ metallness_image = img_tex->get_image();
+ }
+ if (metallness_image->is_compressed()) {
+ metallness_image->decompress();
}
- if (has_metalness) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).a;
+ }
+ Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
+ if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
+ height = albedo_texture->get_height();
+ width = albedo_texture->get_width();
+ }
+ orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8);
+ if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) {
+ ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
+ }
+ if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) {
+ roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
+ }
+ if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) {
+ metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
+ }
+ for (int32_t h = 0; h < height; h++) {
+ for (int32_t w = 0; w < width; w++) {
+ Color c = Color(1.0f, 1.0f, 1.0f);
+ if (has_ao) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).a;
+ }
}
+ if (has_roughness) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).a;
+ }
+ }
+ if (has_metalness) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).a;
+ }
+ }
+ orm_image->set_pixel(w, h, c);
}
- orm_image->set_pixel(w, h, c);
}
- }
- orm_image->generate_mipmaps();
- orm_texture->set_image(orm_image);
- GLTFTextureIndex orm_texture_index = -1;
- if (has_ao || has_roughness || has_metalness) {
- orm_texture->set_name(material->get_name() + "_orm");
- orm_texture_index = _set_texture(p_state, orm_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
- }
- if (has_ao) {
- Dictionary occt;
- occt["index"] = orm_texture_index;
- d["occlusionTexture"] = occt;
- }
- if (has_roughness || has_metalness) {
- mrt["index"] = orm_texture_index;
- Dictionary extensions = _serialize_texture_transform_uv1(material);
- if (!extensions.is_empty()) {
- mrt["extensions"] = extensions;
- p_state->use_khr_texture_transform = true;
+ orm_image->generate_mipmaps();
+ orm_texture->set_image(orm_image);
+ GLTFTextureIndex orm_texture_index = -1;
+ if (has_ao || has_roughness || has_metalness) {
+ orm_texture->set_name(material->get_name() + "_orm");
+ orm_texture_index = _set_texture(p_state, orm_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
+ }
+ if (has_ao) {
+ Dictionary occt;
+ occt["index"] = orm_texture_index;
+ d["occlusionTexture"] = occt;
+ }
+ if (has_roughness || has_metalness) {
+ mrt["index"] = orm_texture_index;
+ Dictionary extensions = _serialize_texture_transform_uv1(material);
+ if (!extensions.is_empty()) {
+ mrt["extensions"] = extensions;
+ p_state->use_khr_texture_transform = true;
+ }
+ mr["metallicRoughnessTexture"] = mrt;
}
- mr["metallicRoughnessTexture"] = mrt;
}
}
d["pbrMetallicRoughness"] = mr;
- if (base_material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) {
+ if (base_material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING) && _image_format != "None") {
Dictionary nt;
Ref<ImageTexture> tex;
tex.instantiate();
@@ -3808,7 +3815,6 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
}
img->decompress();
img->convert(Image::FORMAT_RGBA8);
- img->convert_ra_rgba8_to_rg();
for (int32_t y = 0; y < img->get_height(); y++) {
for (int32_t x = 0; x < img->get_width(); x++) {
Color c = img->get_pixel(x, y);
@@ -3845,7 +3851,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
d["emissiveFactor"] = arr;
}
- if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
+ if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION) && _image_format != "None") {
Dictionary et;
Ref<Texture2D> emission_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_EMISSION);
GLTFTextureIndex gltf_texture_index = -1;
@@ -4408,7 +4414,7 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) {
out_roots.sort();
- ERR_FAIL_COND_V(out_roots.size() == 0, FAILED);
+ ERR_FAIL_COND_V(out_roots.is_empty(), FAILED);
// Make sure the roots are the exact same (they better be)
ERR_FAIL_COND_V(out_roots.size() != p_skin->roots.size(), FAILED);
@@ -4794,10 +4800,10 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> p_state) {
node->set_name(_gen_unique_bone_name(p_state, skel_i, node->get_name()));
skeleton->add_bone(node->get_name());
- skeleton->set_bone_rest(bone_index, node->xform);
- skeleton->set_bone_pose_position(bone_index, node->position);
- skeleton->set_bone_pose_rotation(bone_index, node->rotation.normalized());
- skeleton->set_bone_pose_scale(bone_index, node->scale);
+ skeleton->set_bone_rest(bone_index, node->transform);
+ skeleton->set_bone_pose_position(bone_index, node->get_position());
+ skeleton->set_bone_pose_rotation(bone_index, node->get_rotation());
+ skeleton->set_bone_pose_scale(bone_index, node->get_scale());
if (node->parent >= 0 && p_state->nodes[node->parent]->skeleton == skel_i) {
const int bone_parent = skeleton->find_bone(p_state->nodes[node->parent]->get_name());
@@ -5045,7 +5051,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
AnimationPlayer *animation_player = p_state->animation_players[player_i];
List<StringName> animations;
animation_player->get_animation_list(&animations);
- for (StringName animation_name : animations) {
+ for (const StringName &animation_name : animations) {
_convert_animation(p_state, animation_player, animation_name);
}
}
@@ -5406,14 +5412,13 @@ BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> p_state
GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshInstance3D *p_mesh_instance) {
ERR_FAIL_NULL_V(p_mesh_instance, -1);
- if (p_mesh_instance->get_mesh().is_null()) {
- return -1;
- }
+ ERR_FAIL_COND_V_MSG(p_mesh_instance->get_mesh().is_null(), -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but it has no mesh. This node will be exported without a mesh.");
+ Ref<Mesh> mesh_resource = p_mesh_instance->get_mesh();
+ ERR_FAIL_COND_V_MSG(mesh_resource->get_surface_count() == 0, -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but its mesh has no surfaces. This node will be exported without a mesh.");
- Ref<Mesh> import_mesh = p_mesh_instance->get_mesh();
- Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(import_mesh);
+ Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(mesh_resource);
Vector<float> blend_weights;
- int32_t blend_count = import_mesh->get_blend_shape_count();
+ int32_t blend_count = mesh_resource->get_blend_shape_count();
blend_weights.resize(blend_count);
for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) {
blend_weights.write[blend_i] = 0.0f;
@@ -5503,10 +5508,7 @@ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> p_state, Light3D *p_l
}
void GLTFDocument::_convert_spatial(Ref<GLTFState> p_state, Node3D *p_spatial, Ref<GLTFNode> p_node) {
- Transform3D xform = p_spatial->get_transform();
- p_node->scale = xform.basis.get_scale();
- p_node->rotation = xform.basis.get_rotation_quaternion();
- p_node->position = xform.origin;
+ p_node->transform = p_spatial->get_transform();
}
Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) {
@@ -5524,6 +5526,12 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
if (retflag) {
return;
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint() && p_gltf_root != -1 && p_current->get_owner() == nullptr) {
+ WARN_VERBOSE("glTF export warning: Node '" + p_current->get_name() + "' has no owner. This is likely a temporary node generated by a @tool script. This would not be saved when saving the Godot scene, therefore it will not be exported to glTF.");
+ return;
+ }
+#endif // TOOLS_ENABLED
Ref<GLTFNode> gltf_node;
gltf_node.instantiate();
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
@@ -5620,7 +5628,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
GLTFMeshIndex mesh_i = p_state->meshes.size();
p_state->meshes.push_back(gltf_mesh);
p_gltf_node->mesh = mesh_i;
- p_gltf_node->xform = csg->get_meshes()[0];
+ p_gltf_node->transform = csg->get_meshes()[0];
p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name()));
}
#endif // MODULE_CSG_ENABLED
@@ -5696,7 +5704,7 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex
gltf_mesh->set_mesh(_mesh_to_importer_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell)));
new_gltf_node->mesh = p_state->meshes.size();
p_state->meshes.push_back(gltf_mesh);
- new_gltf_node->xform = cell_xform * p_grid_map->get_transform();
+ new_gltf_node->transform = cell_xform * p_grid_map->get_transform();
new_gltf_node->set_name(_gen_unique_name(p_state, p_grid_map->get_mesh_library()->get_item_name(cell)));
}
}
@@ -5764,7 +5772,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf(
Ref<GLTFNode> new_gltf_node;
new_gltf_node.instantiate();
new_gltf_node->mesh = mesh_index;
- new_gltf_node->xform = transform;
+ new_gltf_node->transform = transform;
new_gltf_node->set_name(_gen_unique_name(p_state, p_multi_mesh_instance->get_name()));
p_gltf_node->children.push_back(p_state->nodes.size());
p_state->nodes.push_back(new_gltf_node);
@@ -5789,10 +5797,7 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS
// Note that we cannot use _gen_unique_bone_name here, because glTF spec requires all node
// names to be unique regardless of whether or not they are used as joints.
joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i)));
- Transform3D xform = skeleton->get_bone_pose(bone_i);
- joint_node->scale = xform.basis.get_scale();
- joint_node->rotation = xform.basis.get_rotation_quaternion();
- joint_node->position = xform.origin;
+ joint_node->transform = skeleton->get_bone_pose(bone_i);
joint_node->joint = true;
GLTFNodeIndex current_node_i = p_state->nodes.size();
p_state->scene_nodes.insert(current_node_i, skeleton);
@@ -5941,7 +5946,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
Array args;
args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
- current_node->set_transform(gltf_node->xform);
+ current_node->set_transform(gltf_node->transform);
}
p_state->scene_nodes.insert(p_node_index, current_node);
@@ -5975,8 +5980,13 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL
p_scene_parent = bone_attachment;
}
if (skeleton->get_parent() == nullptr) {
- p_scene_parent->add_child(skeleton, true);
- skeleton->set_owner(p_scene_root);
+ if (p_scene_root) {
+ p_scene_parent->add_child(skeleton, true);
+ skeleton->set_owner(p_scene_root);
+ } else {
+ p_scene_parent = skeleton;
+ p_scene_root = skeleton;
+ }
}
}
@@ -6071,22 +6081,22 @@ struct SceneFormatImporterGLTFInterpolate {
template <>
struct SceneFormatImporterGLTFInterpolate<Quaternion> {
Quaternion lerp(const Quaternion &a, const Quaternion &b, const float c) const {
- ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quaternion(), "The quaternion \"a\" must be normalized.");
- ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quaternion(), "The quaternion \"b\" must be normalized.");
+ ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quaternion(), vformat("The quaternion \"a\" %s must be normalized.", a));
+ ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quaternion(), vformat("The quaternion \"b\" %s must be normalized.", b));
return a.slerp(b, c).normalized();
}
Quaternion catmull_rom(const Quaternion &p0, const Quaternion &p1, const Quaternion &p2, const Quaternion &p3, const float c) {
- ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quaternion(), "The quaternion \"p1\" must be normalized.");
- ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quaternion(), "The quaternion \"p2\" must be normalized.");
+ ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quaternion(), vformat("The quaternion \"p1\" (%s) must be normalized.", p1));
+ ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quaternion(), vformat("The quaternion \"p2\" (%s) must be normalized.", p2));
return p1.slerp(p2, c).normalized();
}
Quaternion bezier(const Quaternion start, const Quaternion control_1, const Quaternion control_2, const Quaternion end, const float t) {
- ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), "The start quaternion must be normalized.");
- ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), "The end quaternion must be normalized.");
+ ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), vformat("The start quaternion %s must be normalized.", start));
+ ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), vformat("The end quaternion %s must be normalized.", end));
return start.slerp(end, t).normalized();
}
@@ -6094,7 +6104,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
template <class T>
T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
- ERR_FAIL_COND_V(!p_values.size(), T());
+ ERR_FAIL_COND_V(p_values.is_empty(), T());
if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) {
ERR_PRINT_ONCE("The interpolated values are not corresponding to its times.");
return p_values[0];
@@ -6151,9 +6161,9 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]);
- const T from = p_values[idx * 3 + 1];
+ const T &from = p_values[idx * 3 + 1];
const T c1 = from + p_values[idx * 3 + 2];
- const T to = p_values[idx * 3 + 4];
+ const T &to = p_values[idx * 3 + 4];
const T c2 = to + p_values[idx * 3 + 3];
return interp.bezier(from, c1, c2, to, c);
@@ -6268,7 +6278,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (track.position_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Vector3 base_pos = p_state->nodes[track_i.key]->position;
+ Vector3 base_pos = gltf_node->get_position();
for (int i = 0; i < track.position_track.times.size(); i++) {
int value_index = track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.position_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6284,13 +6294,16 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
animation->add_track(Animation::TYPE_POSITION_3D);
animation->track_set_path(position_idx, transform_node_path);
animation->track_set_imported(position_idx, true); //helps merging later
+ if (track.position_track.interpolation == GLTFAnimation::INTERP_STEP) {
+ animation->track_set_interpolation_type(position_idx, Animation::InterpolationType::INTERPOLATION_NEAREST);
+ }
base_idx++;
}
}
if (track.rotation_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized();
+ Quaternion base_rot = gltf_node->get_rotation();
for (int i = 0; i < track.rotation_track.times.size(); i++) {
int value_index = track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.rotation_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6306,13 +6319,16 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
animation->add_track(Animation::TYPE_ROTATION_3D);
animation->track_set_path(rotation_idx, transform_node_path);
animation->track_set_imported(rotation_idx, true); //helps merging later
+ if (track.rotation_track.interpolation == GLTFAnimation::INTERP_STEP) {
+ animation->track_set_interpolation_type(rotation_idx, Animation::InterpolationType::INTERPOLATION_NEAREST);
+ }
base_idx++;
}
}
if (track.scale_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Vector3 base_scale = p_state->nodes[track_i.key]->scale;
+ Vector3 base_scale = gltf_node->get_scale();
for (int i = 0; i < track.scale_track.times.size(); i++) {
int value_index = track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.scale_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6328,6 +6344,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
animation->add_track(Animation::TYPE_SCALE_3D);
animation->track_set_path(scale_idx, transform_node_path);
animation->track_set_imported(scale_idx, true); //helps merging later
+ if (track.scale_track.interpolation == GLTFAnimation::INTERP_STEP) {
+ animation->track_set_interpolation_type(scale_idx, Animation::InterpolationType::INTERPOLATION_NEAREST);
+ }
base_idx++;
}
}
@@ -6340,15 +6359,15 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
Vector3 base_scale = Vector3(1, 1, 1);
if (rotation_idx == -1) {
- base_rot = p_state->nodes[track_i.key]->rotation.normalized();
+ base_rot = gltf_node->get_rotation();
}
if (position_idx == -1) {
- base_pos = p_state->nodes[track_i.key]->position;
+ base_pos = gltf_node->get_position();
}
if (scale_idx == -1) {
- base_scale = p_state->nodes[track_i.key]->scale;
+ base_scale = gltf_node->get_scale();
}
bool last = false;
@@ -6455,10 +6474,7 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> p_state) {
if (!mi) {
continue;
}
- Transform3D mi_xform = mi->get_transform();
- node->scale = mi_xform.basis.get_scale();
- node->rotation = mi_xform.basis.get_rotation_quaternion();
- node->position = mi_xform.origin;
+ node->transform = mi->get_transform();
Node *skel_node = mi->get_node_or_null(mi->get_skeleton_path());
Skeleton3D *godot_skeleton = Object::cast_to<Skeleton3D>(skel_node);
@@ -6566,7 +6582,7 @@ float GLTFDocument::get_max_component(const Color &p_color) {
return MAX(MAX(r, g), b);
}
-void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) {
+void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) {
for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) {
Ref<GLTFNode> node = p_state->nodes[node_i];
@@ -6591,7 +6607,7 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) {
mi->get_parent()->remove_child(mi);
skeleton->add_child(mi, true);
- mi->set_owner(skeleton->get_owner());
+ mi->set_owner(p_scene_root);
mi->set_skin(p_state->skins.write[skin_i]->godot_skin);
mi->set_skeleton_path(mi->get_path_to(skeleton));
@@ -7052,9 +7068,9 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
} else if (String(final_track_path).contains(":")) {
//Process skeleton
const Vector<String> node_suffix = String(final_track_path).split(":");
- const String node = node_suffix[0];
+ const String &node = node_suffix[0];
const NodePath node_path = node;
- const String suffix = node_suffix[1];
+ const String &suffix = node_suffix[1];
Node *godot_node = animation_base_node->get_node_or_null(node_path);
if (!godot_node) {
continue;
@@ -7336,6 +7352,10 @@ void GLTFDocument::unregister_all_gltf_document_extensions() {
all_document_extensions.clear();
}
+Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extensions() {
+ return all_document_extensions;
+}
+
PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) {
Error err = _encode_buffer_glb(p_state, "");
if (r_err) {
@@ -7441,7 +7461,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo
Error err = OK;
Node *root = _generate_scene_node_tree(p_state);
ERR_FAIL_NULL_V(root, nullptr);
- _process_mesh_instances(p_state);
+ _process_mesh_instances(p_state, root);
if (p_state->get_create_animations() && p_state->animations.size()) {
AnimationPlayer *ap = memnew(AnimationPlayer);
root->add_child(ap, true);
@@ -7454,11 +7474,13 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo
ERR_CONTINUE(!E.value);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- ERR_CONTINUE(!p_state->json.has("nodes"));
- Array nodes = p_state->json["nodes"];
- ERR_CONTINUE(E.key >= nodes.size());
- ERR_CONTINUE(E.key < 0);
- Dictionary node_json = nodes[E.key];
+ Dictionary node_json;
+ if (p_state->json.has("nodes")) {
+ Array nodes = p_state->json["nodes"];
+ if (0 <= E.key && E.key < nodes.size()) {
+ node_json = nodes[E.key];
+ }
+ }
Ref<GLTFNode> gltf_node = p_state->nodes[E.key];
err = ext->import_node(p_state, gltf_node, node_json, E.value);
ERR_CONTINUE(err != OK);
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 7e378fe94d..04c85f3b07 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -86,6 +86,7 @@ public:
static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
static void unregister_all_gltf_document_extensions();
+ static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions();
void set_naming_version(int p_version);
int get_naming_version() const;
@@ -325,7 +326,7 @@ public:
Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path);
Error _parse_asset_header(Ref<GLTFState> p_state);
Error _parse_gltf_extensions(Ref<GLTFState> p_state);
- void _process_mesh_instances(Ref<GLTFState> p_state);
+ void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root);
Node *_generate_scene_node_tree(Ref<GLTFState> p_state);
void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 766fe41257..0c47d5777c 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -34,6 +34,8 @@
void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension);
+ ClassDB::bind_method(D_METHOD("append_data_to_buffers", "data", "deduplication"), &GLTFState::append_data_to_buffers);
+
ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json);
ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json);
ClassDB::bind_method(D_METHOD("get_major_version"), &GLTFState::get_major_version);
@@ -399,3 +401,29 @@ Variant GLTFState::get_additional_data(const StringName &p_extension_name) {
void GLTFState::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}
+
+GLTFBufferViewIndex GLTFState::append_data_to_buffers(const Vector<uint8_t> &p_data, const bool p_deduplication = false) {
+ if (p_deduplication) {
+ for (int i = 0; i < buffer_views.size(); i++) {
+ Ref<GLTFBufferView> buffer_view = buffer_views[i];
+ Vector<uint8_t> buffer_view_data = buffer_view->load_buffer_view_data(this);
+ if (buffer_view_data == p_data) {
+ return i;
+ }
+ }
+ }
+ // Append the given data to a buffer and create a buffer view for it.
+ if (unlikely(buffers.is_empty())) {
+ buffers.push_back(Vector<uint8_t>());
+ }
+ Vector<uint8_t> &destination_buffer = buffers.write[0];
+ Ref<GLTFBufferView> buffer_view;
+ buffer_view.instantiate();
+ buffer_view->set_buffer(0);
+ buffer_view->set_byte_offset(destination_buffer.size());
+ buffer_view->set_byte_length(p_data.size());
+ destination_buffer.append_array(p_data);
+ const int new_index = buffer_views.size();
+ buffer_views.push_back(buffer_view);
+ return new_index;
+}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 1ed8ce3629..3edf9a722f 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -105,6 +105,7 @@ protected:
public:
void add_used_extension(const String &p_extension, bool p_required = false);
+ GLTFBufferViewIndex append_data_to_buffers(const Vector<uint8_t> &p_data, const bool p_deduplication);
enum GLTFHandleBinary {
HANDLE_BINARY_DISCARD_TEXTURES = 0,
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 0bf02cf890..216309abf1 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -55,23 +55,39 @@ static void _editor_init() {
// Blend to glTF importer.
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
+ if (blender_path.is_empty() && EditorSettings::get_singleton()->has_setting("filesystem/import/blender/blender3_path")) {
+ blender_path = EditorSettings::get_singleton()->get("filesystem/import/blender/blender3_path");
+
+ if (!blender_path.is_empty()) {
+#if defined(MACOS_ENABLED)
+ if (blender_path.contains(".app")) {
+ blender_path += "/Contents/MacOS/Blender";
+ } else {
+ blender_path += "/blender";
+ }
+#elif defined(WINDOWS_ENABLED)
+ blender_path += "\\blender.exe";
+#elif defined(UNIX_ENABLED)
+ blender_path += "/blender";
+#endif
+
+ EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path);
+ }
+
+ EditorSettings::get_singleton()->erase("filesystem/import/blender/blender3_path");
+ EditorSettings::get_singleton()->save();
+ }
+
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- String blender3_path = EDITOR_GET("filesystem/import/blender/blender3_path");
if (blend_enabled) {
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (blender3_path.is_empty()) {
- WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported."));
- } else if (!da->dir_exists(blender3_path)) {
- WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported."));
- } else {
- Ref<EditorSceneFormatImporterBlend> importer;
- importer.instantiate();
- ResourceImporterScene::add_scene_importer(importer);
-
- Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query;
- blend_import_query.instantiate();
- EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query);
- }
+ Ref<EditorSceneFormatImporterBlend> importer;
+ importer.instantiate();
+ ResourceImporterScene::add_scene_importer(importer);
+
+ Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query;
+ blend_import_query.instantiate();
+ EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query);
}
memnew(EditorImportBlendRunner);
EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton());
@@ -118,6 +134,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFTexture);
GDREGISTER_CLASS(GLTFTextureSampler);
// Register GLTFDocumentExtension classes with GLTFDocument.
+ // Ensure physics is first in this list so that physics nodes are created before other nodes.
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureWebP);
diff --git a/modules/gltf/structures/gltf_buffer_view.compat.inc b/modules/gltf/structures/gltf_buffer_view.compat.inc
new file mode 100644
index 0000000000..db2600a071
--- /dev/null
+++ b/modules/gltf/structures/gltf_buffer_view.compat.inc
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* gltf_buffer_view.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+GLTFBufferIndex GLTFBufferView::_get_buffer_bind_compat_86907() {
+ return get_buffer();
+}
+
+int GLTFBufferView::_get_byte_offset_bind_compat_86907() {
+ return get_byte_offset();
+}
+
+int GLTFBufferView::_get_byte_length_bind_compat_86907() {
+ return get_byte_length();
+}
+
+int GLTFBufferView::_get_byte_stride_bind_compat_86907() {
+ return get_byte_stride();
+}
+
+bool GLTFBufferView::_get_indices_bind_compat_86907() {
+ return get_indices();
+}
+
+void GLTFBufferView::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("get_buffer"), &GLTFBufferView::_get_buffer_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_offset"), &GLTFBufferView::_get_byte_offset_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_length"), &GLTFBufferView::_get_byte_length_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_stride"), &GLTFBufferView::_get_byte_stride_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_indices"), &GLTFBufferView::_get_indices_bind_compat_86907);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp
index 7678f23f57..997c219bf0 100644
--- a/modules/gltf/structures/gltf_buffer_view.cpp
+++ b/modules/gltf/structures/gltf_buffer_view.cpp
@@ -29,8 +29,13 @@
/**************************************************************************/
#include "gltf_buffer_view.h"
+#include "gltf_buffer_view.compat.inc"
+
+#include "../gltf_state.h"
void GLTFBufferView::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("load_buffer_view_data", "state"), &GLTFBufferView::load_buffer_view_data);
+
ClassDB::bind_method(D_METHOD("get_buffer"), &GLTFBufferView::get_buffer);
ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &GLTFBufferView::set_buffer);
ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFBufferView::get_byte_offset);
@@ -49,7 +54,7 @@ void GLTFBufferView::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indices"), "set_indices", "get_indices"); // bool
}
-GLTFBufferIndex GLTFBufferView::get_buffer() {
+GLTFBufferIndex GLTFBufferView::get_buffer() const {
return buffer;
}
@@ -57,7 +62,7 @@ void GLTFBufferView::set_buffer(GLTFBufferIndex p_buffer) {
buffer = p_buffer;
}
-int GLTFBufferView::get_byte_offset() {
+int GLTFBufferView::get_byte_offset() const {
return byte_offset;
}
@@ -65,7 +70,7 @@ void GLTFBufferView::set_byte_offset(int p_byte_offset) {
byte_offset = p_byte_offset;
}
-int GLTFBufferView::get_byte_length() {
+int GLTFBufferView::get_byte_length() const {
return byte_length;
}
@@ -73,7 +78,7 @@ void GLTFBufferView::set_byte_length(int p_byte_length) {
byte_length = p_byte_length;
}
-int GLTFBufferView::get_byte_stride() {
+int GLTFBufferView::get_byte_stride() const {
return byte_stride;
}
@@ -81,10 +86,20 @@ void GLTFBufferView::set_byte_stride(int p_byte_stride) {
byte_stride = p_byte_stride;
}
-bool GLTFBufferView::get_indices() {
+bool GLTFBufferView::get_indices() const {
return indices;
}
void GLTFBufferView::set_indices(bool p_indices) {
indices = p_indices;
}
+
+Vector<uint8_t> GLTFBufferView::load_buffer_view_data(const Ref<GLTFState> p_state) const {
+ ERR_FAIL_COND_V(p_state.is_null(), Vector<uint8_t>());
+ ERR_FAIL_COND_V_MSG(byte_stride > 0, Vector<uint8_t>(), "Buffer views with byte stride are not yet supported by this method.");
+ const TypedArray<Vector<uint8_t>> &buffers = p_state->get_buffers();
+ ERR_FAIL_INDEX_V(buffer, buffers.size(), Vector<uint8_t>());
+ const PackedByteArray &buffer_data = buffers[buffer];
+ const int64_t byte_end = byte_offset + byte_length;
+ return buffer_data.slice(byte_offset, byte_end);
+}
diff --git a/modules/gltf/structures/gltf_buffer_view.h b/modules/gltf/structures/gltf_buffer_view.h
index 6d138dbf11..1c7bd5c5c7 100644
--- a/modules/gltf/structures/gltf_buffer_view.h
+++ b/modules/gltf/structures/gltf_buffer_view.h
@@ -49,22 +49,32 @@ private:
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ GLTFBufferIndex _get_buffer_bind_compat_86907();
+ int _get_byte_offset_bind_compat_86907();
+ int _get_byte_length_bind_compat_86907();
+ int _get_byte_stride_bind_compat_86907();
+ bool _get_indices_bind_compat_86907();
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
public:
- GLTFBufferIndex get_buffer();
+ GLTFBufferIndex get_buffer() const;
void set_buffer(GLTFBufferIndex p_buffer);
- int get_byte_offset();
+ int get_byte_offset() const;
void set_byte_offset(int p_byte_offset);
- int get_byte_length();
+ int get_byte_length() const;
void set_byte_length(int p_byte_length);
- int get_byte_stride();
+ int get_byte_stride() const;
void set_byte_stride(int p_byte_stride);
- bool get_indices();
+ bool get_indices() const;
void set_indices(bool p_indices);
- // matrices need to be transformed to this
+
+ Vector<uint8_t> load_buffer_view_data(const Ref<GLTFState> p_state) const;
};
#endif // GLTF_BUFFER_VIEW_H
diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp
index 30895034a9..03f71525fd 100644
--- a/modules/gltf/structures/gltf_node.cpp
+++ b/modules/gltf/structures/gltf_node.cpp
@@ -89,11 +89,11 @@ void GLTFNode::set_height(int p_height) {
}
Transform3D GLTFNode::get_xform() {
- return xform;
+ return transform;
}
void GLTFNode::set_xform(Transform3D p_xform) {
- xform = p_xform;
+ transform = p_xform;
}
GLTFMeshIndex GLTFNode::get_mesh() {
@@ -129,27 +129,27 @@ void GLTFNode::set_skeleton(GLTFSkeletonIndex p_skeleton) {
}
Vector3 GLTFNode::get_position() {
- return position;
+ return transform.origin;
}
void GLTFNode::set_position(Vector3 p_position) {
- position = p_position;
+ transform.origin = p_position;
}
Quaternion GLTFNode::get_rotation() {
- return rotation;
+ return transform.basis.get_rotation_quaternion();
}
void GLTFNode::set_rotation(Quaternion p_rotation) {
- rotation = p_rotation;
+ transform.basis.set_quaternion_scale(p_rotation, transform.basis.get_scale());
}
Vector3 GLTFNode::get_scale() {
- return scale;
+ return transform.basis.get_scale();
}
void GLTFNode::set_scale(Vector3 p_scale) {
- scale = p_scale;
+ transform.basis = transform.basis.orthonormalized() * Basis::from_scale(p_scale);
}
Vector<int> GLTFNode::get_children() {
diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h
index c2d2f64495..aba7374127 100644
--- a/modules/gltf/structures/gltf_node.h
+++ b/modules/gltf/structures/gltf_node.h
@@ -40,18 +40,14 @@ class GLTFNode : public Resource {
friend class GLTFDocument;
private:
- // matrices need to be transformed to this
GLTFNodeIndex parent = -1;
int height = -1;
- Transform3D xform;
+ Transform3D transform;
GLTFMeshIndex mesh = -1;
GLTFCameraIndex camera = -1;
GLTFSkinIndex skin = -1;
GLTFSkeletonIndex skeleton = -1;
bool joint = false;
- Vector3 position;
- Quaternion rotation;
- Vector3 scale = Vector3(1, 1, 1);
Vector<int> children;
GLTFLightIndex light = -1;
Dictionary additional_data;
diff --git a/modules/gltf/structures/gltf_skeleton.h b/modules/gltf/structures/gltf_skeleton.h
index 72a4a06e5c..db5ce7e338 100644
--- a/modules/gltf/structures/gltf_skeleton.h
+++ b/modules/gltf/structures/gltf_skeleton.h
@@ -70,29 +70,29 @@ public:
Skeleton3D *get_godot_skeleton();
// Skeleton *get_godot_skeleton() {
- // return this->godot_skeleton;
+ // return godot_skeleton;
// }
// void set_godot_skeleton(Skeleton p_*godot_skeleton) {
- // this->godot_skeleton = p_godot_skeleton;
+ // godot_skeleton = p_godot_skeleton;
// }
TypedArray<String> get_unique_names();
void set_unique_names(TypedArray<String> p_unique_names);
//RBMap<int32_t, GLTFNodeIndex> get_godot_bone_node() {
- // return this->godot_bone_node;
+ // return godot_bone_node;
//}
- //void set_godot_bone_node(RBMap<int32_t, GLTFNodeIndex> p_godot_bone_node) {
- // this->godot_bone_node = p_godot_bone_node;
+ //void set_godot_bone_node(const RBMap<int32_t, GLTFNodeIndex> &p_godot_bone_node) {
+ // godot_bone_node = p_godot_bone_node;
//}
Dictionary get_godot_bone_node();
void set_godot_bone_node(Dictionary p_indict);
//Dictionary get_godot_bone_node() {
- // return VariantConversion::to_dict(this->godot_bone_node);
+ // return VariantConversion::to_dict(godot_bone_node);
//}
//void set_godot_bone_node(Dictionary p_indict) {
- // VariantConversion::set_from_dict(this->godot_bone_node, p_indict);
+ // VariantConversion::set_from_dict(godot_bone_node, p_indict);
//}
BoneAttachment3D *get_bone_attachment(int idx);
diff --git a/modules/gltf/structures/gltf_skin.h b/modules/gltf/structures/gltf_skin.h
index 164cabfe12..cd5f2d9ca2 100644
--- a/modules/gltf/structures/gltf_skin.h
+++ b/modules/gltf/structures/gltf_skin.h
@@ -34,6 +34,7 @@
#include "../gltf_defines.h"
#include "core/io/resource.h"
+#include "scene/resources/skin.h"
template <typename T>
class TypedArray;
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index f7c01ff840..b8902694c9 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -36,11 +36,11 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
+#include "editor/themes/editor_scale.h"
#include "scene/3d/camera_3d.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/label.h"
@@ -1316,6 +1316,7 @@ GridMapEditor::GridMapEditor() {
EDITOR_DEF("editors/grid_map/preview_size", 64);
mesh_library_palette = memnew(ItemList);
+ mesh_library_palette->set_auto_translate(false);
add_child(mesh_library_palette);
mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL);
mesh_library_palette->connect("gui_input", callable_mp(this, &GridMapEditor::_mesh_library_palette_input));
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index 6f493f48e3..e3475b3959 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -32,7 +32,6 @@
#include "core/core_string_names.h"
#include "core/io/marshalls.h"
-#include "core/object/message_queue.h"
#include "scene/3d/light_3d.h"
#include "scene/resources/mesh_library.h"
#include "scene/resources/physics_material.h"
@@ -975,7 +974,7 @@ void GridMap::_queue_octants_dirty() {
return;
}
- MessageQueue::get_singleton()->push_call(this, "_update_octants_callback");
+ callable_mp(this, &GridMap::_update_octants_callback).call_deferred();
awaiting_update = true;
}
@@ -1082,7 +1081,6 @@ void GridMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("local_to_map", "local_position"), &GridMap::local_to_map);
ClassDB::bind_method(D_METHOD("map_to_local", "map_position"), &GridMap::map_to_local);
- ClassDB::bind_method(D_METHOD("_update_octants_callback"), &GridMap::_update_octants_callback);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &GridMap::resource_changed);
#endif
diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp
index e7fa909706..ada0cd01fa 100644
--- a/modules/jpg/image_loader_jpegd.cpp
+++ b/modules/jpg/image_loader_jpegd.cpp
@@ -162,6 +162,7 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c
ERR_FAIL_COND_V_MSG(error != OK, error, "Couldn't decompress image.");
}
if (image->get_format() != Image::FORMAT_RGB8) {
+ image = p_img->duplicate();
image->convert(Image::FORMAT_RGB8);
}
@@ -173,12 +174,16 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c
const uint8_t *src_data = image->get_data().ptr();
for (int i = 0; i < image->get_height(); i++) {
- enc.process_scanline(&src_data[i * image->get_width() * 3]);
+ if (!enc.process_scanline(&src_data[i * image->get_width() * 3])) {
+ return FAILED;
+ }
}
- enc.process_scanline(nullptr);
-
- return OK;
+ if (enc.process_scanline(nullptr)) {
+ return OK;
+ } else {
+ return FAILED;
+ }
}
static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) {
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index a4ecb767a7..5d22cb1301 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -41,6 +41,10 @@
#include "editor/editor_settings.h"
#include "servers/rendering/rendering_device_binds.h"
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
+#endif
+
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
@@ -49,7 +53,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) {
ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->is_empty());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height());
- ERR_FAIL_COND(p_mesh.points.size() == 0);
+ ERR_FAIL_COND(p_mesh.points.is_empty());
MeshInstance mi;
mi.data = p_mesh;
mesh_instances.push_back(mi);
@@ -357,7 +361,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
if (p_step_function) {
- float p = float(m_i + 1) / mesh_instances.size() * 0.1;
+ float p = float(m_i + 1) / MAX(1, mesh_instances.size()) * 0.1;
p_step_function(0.3 + p, vformat(RTR("Plotting mesh into acceleration structure %d/%d"), m_i + 1, mesh_instances.size()), p_bake_userdata, false);
}
@@ -703,7 +707,7 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x);
raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y);
- RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
//draw opaque
rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
@@ -756,7 +760,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
push_constant.region_ofs[0] = 0;
push_constant.region_ofs[1] = 0;
- Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); //restore group size
+ Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
@@ -769,7 +773,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
- Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
+ Vector<uint8_t> s = rd->texture_get_data(source_light_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->convert(Image::FORMAT_RGBA8);
img->save_png("res://5_dilated_" + itos(i) + ".png");
@@ -939,8 +943,8 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
// We use a region with 1/4 the amount of pixels if we're denoising SH lightmaps, as
// all four of them are denoised in the shader in one dispatch.
const int max_region_size = p_bake_sh ? 512 : 1024;
- int x_regions = (p_atlas_size.width - 1) / max_region_size + 1;
- int y_regions = (p_atlas_size.height - 1) / max_region_size + 1;
+ int x_regions = Math::division_round_up(p_atlas_size.width, max_region_size);
+ int y_regions = Math::division_round_up(p_atlas_size.height, max_region_size);
for (int s = 0; s < p_atlas_slices; s++) {
p_push_constant.atlas_slice = s;
@@ -958,7 +962,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
p_rd->compute_list_bind_uniform_set(compute_list, p_compute_base_uniform_set, 0);
p_rd->compute_list_bind_uniform_set(compute_list, denoise_uniform_set, 1);
p_rd->compute_list_set_push_constant(compute_list, &p_push_constant, sizeof(PushConstant));
- p_rd->compute_list_dispatch(compute_list, (w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
+ p_rd->compute_list_dispatch(compute_list, Math::division_round_up(w, 8), Math::division_round_up(h, 8), 1);
p_rd->compute_list_end();
p_rd->submit();
@@ -1017,7 +1021,35 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
#endif
- RenderingDevice *rd = RenderingDevice::get_singleton()->create_local_device();
+ // Attempt to create a local device by requesting it from rendering server first.
+ // If that fails because the current renderer is not implemented on top of RD, we fall back to creating
+ // a local rendering device manually depending on the current platform.
+ Error err;
+ RenderingContextDriver *rcd = nullptr;
+ RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
+ if (rd == nullptr) {
+#if defined(RD_ENABLED)
+#if defined(VULKAN_ENABLED)
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+#endif
+#endif
+ if (rcd != nullptr && rd != nullptr) {
+ err = rcd->initialize();
+ if (err == OK) {
+ err = rd->initialize(rcd);
+ }
+
+ if (err != OK) {
+ memdelete(rd);
+ memdelete(rcd);
+ rd = nullptr;
+ rcd = nullptr;
+ }
+ }
+ }
+
+ ERR_FAIL_NULL_V(rd, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
RID albedo_array_tex;
RID emission_array_tex;
@@ -1187,7 +1219,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
//shaders
Ref<RDShaderFile> raster_shader;
raster_shader.instantiate();
- Error err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
+ err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
if (err != OK) {
raster_shader->print_errors("raster_shader");
@@ -1195,6 +1227,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_BUFFERS
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1367,6 +1403,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_BUFFERS
FREE_RASTER_RESOURCES
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
compute_shader->print_errors("compute_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1399,7 +1440,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
rd->free(compute_shader_secondary); \
rd->free(compute_shader_light_probes);
- Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1);
+ Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1);
rd->submit();
rd->sync();
@@ -1592,10 +1633,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size")));
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass");
- int x_regions = (atlas_size.width - 1) / max_region_size + 1;
- int y_regions = (atlas_size.height - 1) / max_region_size + 1;
+ int x_regions = Math::division_round_up(atlas_size.width, max_region_size);
+ int y_regions = Math::division_round_up(atlas_size.height, max_region_size);
- int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
+ int ray_iterations = Math::division_round_up((int32_t)push_constant.ray_count, max_rays);
rd->submit();
rd->sync();
@@ -1614,7 +1655,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.region_ofs[0] = x;
push_constant.region_ofs[1] = y;
- group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
+ group_size = Vector3i(Math::division_round_up(w, 8), Math::division_round_up(h, 8), 1);
for (int k = 0; k < ray_iterations; k++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
@@ -1700,7 +1741,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.probe_count = probe_positions.size();
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass");
- int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
+ int ray_iterations = Math::division_round_up((int32_t)push_constant.ray_count, max_rays);
for (int i = 0; i < ray_iterations; i++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
@@ -1711,7 +1752,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.ray_from = i * max_rays;
push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
- rd->compute_list_dispatch(compute_list, (probe_positions.size() - 1) / 64 + 1, 1, 1);
+ rd->compute_list_dispatch(compute_list, Math::division_round_up((int)probe_positions.size(), 64), 1, 1);
rd->compute_list_end(); //done
rd->submit();
@@ -1789,6 +1830,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_RASTER_RESOURCES
FREE_COMPUTE_RESOURCES
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
blendseams_shader->print_errors("blendseams_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1863,7 +1909,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
seams_push_constant.slice = uint32_t(i * subslices + k);
seams_push_constant.debug = debug;
- RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1);
@@ -1964,6 +2010,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
memdelete(rd);
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
return BAKE_OK;
}
@@ -1986,7 +2036,7 @@ Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const {
}
Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
- ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2());
+ ERR_FAIL_COND_V(bake_textures.is_empty(), Rect2());
Rect2 uv_ofs;
Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height());
uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index a2a480043a..1d088450e9 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -13,6 +13,8 @@ denoise = "#define MODE_DENOISE";
#VERSION_DEFINES
+#extension GL_EXT_samplerless_texture_functions : enable
+
// One 2D local group focusing in one layer at a time, though all
// in parallel (no barriers) makes more sense than a 3D local group
// as this can take more advantage of the cache for each group.
@@ -152,7 +154,7 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
uint iters = 0;
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) {
- uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy;
+ uvec2 cell_data = texelFetch(grid, icell, 0).xy;
uint triangle_count = cell_data.x;
if (triangle_count > 0) {
uint hit = RAY_MISS;
@@ -264,7 +266,16 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
break;
}
- bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy));
+ // There should be only one axis updated at a time for DDA to work properly.
+ bvec3 mask = bvec3(true, false, false);
+ float m = side.x;
+ if (side.y < m) {
+ m = side.y;
+ mask = bvec3(false, true, false);
+ }
+ if (side.z < m) {
+ mask = bvec3(false, false, true);
+ }
side += vec3(mask) * delta;
icell += ivec3(vec3(mask)) * step;
iters++;
diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp
index 8103799974..4fe8f20723 100644
--- a/modules/lightmapper_rd/register_types.cpp
+++ b/modules/lightmapper_rd/register_types.cpp
@@ -46,18 +46,18 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
return;
}
- GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 32);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 128);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 512);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 2048);
- GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_pass", 32);
- GLOBAL_DEF("rendering/lightmapping/bake_performance/region_size", 512);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/low_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 32);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/medium_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 128);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/high_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/ultra_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 2048);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/max_rays_per_pass", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 32);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/region_size", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_probe_ray_count", 64);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_probe_ray_count", 256);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512);
- GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048);
- GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/low_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 64);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/medium_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 256);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/high_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 2048);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/max_rays_per_probe_pass", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/denoising/denoiser", PROPERTY_HINT_ENUM, "JNLM,OIDN"), 0);
#ifndef _3D_DISABLED
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 4efa4d329e..a46a1c93b5 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -46,7 +46,9 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
int frames_mixed_this_step = p_frames;
int beat_length_frames = -1;
- bool beat_loop = mp3_stream->has_loop() && mp3_stream->get_bpm() > 0 && mp3_stream->get_beat_count() > 0;
+ bool use_loop = looping_override ? looping : mp3_stream->loop;
+
+ bool beat_loop = use_loop && mp3_stream->get_bpm() > 0 && mp3_stream->get_beat_count() > 0;
if (beat_loop) {
beat_length_frames = mp3_stream->get_beat_count() * mp3_stream->sample_rate * 60 / mp3_stream->get_bpm();
}
@@ -82,7 +84,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
else {
//EOF
- if (mp3_stream->loop) {
+ if (use_loop) {
seek(mp3_stream->loop_offset);
loops++;
} else {
@@ -143,6 +145,25 @@ void AudioStreamPlaybackMP3::tag_used_streams() {
mp3_stream->tag_used(get_playback_position());
}
+void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) {
+ if (p_name == SNAME("looping")) {
+ if (p_value == Variant()) {
+ looping_override = false;
+ looping = false;
+ } else {
+ looping_override = true;
+ looping = p_value;
+ }
+ }
+}
+
+Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const {
+ if (looping_override && p_name == SNAME("looping")) {
+ return looping;
+ }
+ return Variant();
+}
+
AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() {
if (mp3d) {
mp3dec_ex_close(mp3d);
@@ -232,6 +253,10 @@ bool AudioStreamMP3::is_monophonic() const {
return false;
}
+void AudioStreamMP3::get_parameter_list(List<Parameter> *r_parameters) {
+ r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant()));
+}
+
void AudioStreamMP3::set_bpm(double p_bpm) {
ERR_FAIL_COND(p_bpm < 0);
bpm = p_bpm;
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index 30760703e3..7d85e0a321 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -47,6 +47,8 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
AudioFrame loop_fade[FADE_SIZE];
int loop_fade_remaining = FADE_SIZE;
+ bool looping_override = false;
+ bool looping = false;
mp3dec_ex_t *mp3d = nullptr;
uint32_t frames_mixed = 0;
bool active = false;
@@ -72,6 +74,9 @@ public:
virtual void tag_used_streams() override;
+ virtual void set_parameter(const StringName &p_name, const Variant &p_value) override;
+ virtual Variant get_parameter(const StringName &p_name) const override;
+
AudioStreamPlaybackMP3() {}
~AudioStreamPlaybackMP3();
};
@@ -126,6 +131,8 @@ public:
virtual bool is_monophonic() const override;
+ virtual void get_parameter_list(List<Parameter> *r_parameters) override;
+
AudioStreamMP3();
virtual ~AudioStreamMP3();
};
diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp
index 33c926689a..e4b54ef050 100644
--- a/modules/minimp3/resource_importer_mp3.cpp
+++ b/modules/minimp3/resource_importer_mp3.cpp
@@ -110,7 +110,7 @@ Ref<AudioStreamMP3> ResourceImporterMP3::import_mp3(const String &p_path) {
mp3_stream.instantiate();
mp3_stream->set_data(data);
- ERR_FAIL_COND_V(!mp3_stream->get_data().size(), Ref<AudioStreamMP3>());
+ ERR_FAIL_COND_V(mp3_stream->get_data().is_empty(), Ref<AudioStreamMP3>());
return mp3_stream;
}
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 564c5929c7..d267df938a 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -18,12 +18,6 @@ env_mono.add_source_files(env.modules_sources, "glue/*.cpp")
env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp")
env_mono.add_source_files(env.modules_sources, "utils/*.cpp")
-env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp")
-
-if env["platform"] in ["macos", "ios"]:
- env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm")
- env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m")
-
if env.editor_build:
env_mono.add_source_files(env.modules_sources, "editor/*.cpp")
SConscript("editor/script_templates/SCsub")
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 8e1587997b..e08491729b 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -371,7 +371,7 @@ Ref<Script> CSharpLanguage::make_template(const String &p_template, const String
return scr;
}
-Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) {
+Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(const StringName &p_object) {
Vector<ScriptLanguage::ScriptTemplate> templates;
#ifdef TOOLS_ENABLED
for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) {
@@ -558,42 +558,9 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
}
String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
- Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type());
- // Always assign r_base_type and r_icon_path, even if the script
- // is not a global one. In the case that it is not a global script,
- // return an empty string AFTER assigning the return parameters.
- // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp
-
- if (!scr.is_valid() || !scr->valid) {
- // Invalid script.
- return String();
- }
-
- if (r_icon_path) {
- if (scr->icon_path.is_empty() || scr->icon_path.is_absolute_path()) {
- *r_icon_path = scr->icon_path.simplify_path();
- } else if (scr->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(scr->icon_path).simplify_path();
- }
- }
- if (r_base_type) {
- bool found_global_base_script = false;
- const CSharpScript *top = scr->base_script.ptr();
- while (top != nullptr) {
- if (top->global_class) {
- *r_base_type = top->class_name;
- found_global_base_script = true;
- break;
- }
-
- top = top->base_script.ptr();
- }
- if (!found_global_base_script) {
- *r_base_type = scr->get_instance_base_type();
- }
- }
-
- return scr->global_class ? scr->class_name : String();
+ String class_name;
+ GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, &class_name);
+ return class_name;
}
String CSharpLanguage::debug_get_error() const {
@@ -720,9 +687,15 @@ void CSharpLanguage::reload_all_scripts() {
#endif
}
-void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
- (void)p_script; // UNUSED
+void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
+#ifdef GD_MONO_HOT_RELOAD
+ if (is_assembly_reloading_needed()) {
+ reload_assemblies(p_soft_reload);
+ }
+#endif
+}
+void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
@@ -925,7 +898,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
- scr->was_tool_before_reload = scr->tool;
+ scr->was_tool_before_reload = scr->type_info.is_tool;
scr->_clear();
}
@@ -985,7 +958,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->exports_invalidated = true;
#endif
- if (!scr->get_path().is_empty()) {
+ if (!scr->get_path().is_empty() && !scr->get_path().begins_with("csharp://")) {
scr->reload(p_soft_reload);
if (!scr->valid) {
@@ -1074,7 +1047,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
// The script instance could not be instantiated or wasn't in the list of placeholders to replace.
obj->set_script(scr);
-#if DEBUG_ENABLED
+#ifdef DEBUG_ENABLED
// If we reached here, the instantiated script must be a placeholder.
CRASH_COND(!obj->get_script_instance()->is_placeholder());
#endif
@@ -1827,6 +1800,7 @@ bool CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, false);
ERR_FAIL_COND_V(script.is_null(), false);
+ ERR_FAIL_COND_V(!script->can_instantiate(), false);
bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance(
script.ptr(), owner, nullptr, 0);
@@ -2149,7 +2123,7 @@ void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript
#ifdef TOOLS_ENABLED
p_script->exported_members_cache.push_back(PropertyInfo(
- Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE,
+ Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
p_script->get_path(), PROPERTY_USAGE_CATEGORY));
#endif
@@ -2246,6 +2220,17 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
} else {
p_instance_to_update->update(propnames, values);
}
+ } else if (placeholders.size()) {
+ uint64_t script_modified_time = FileAccess::get_modified_time(get_path());
+ uint64_t last_valid_build_time = GDMono::get_singleton()->get_project_assembly_modified_time();
+ if (script_modified_time > last_valid_build_time) {
+ for (PlaceHolderScriptInstance *instance : placeholders) {
+ Object *owner = instance->get_owner();
+ if (owner->get_script_instance() == instance) {
+ owner->notify_property_list_changed();
+ }
+ }
+ }
}
}
#endif
@@ -2299,7 +2284,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
p_script->_update_exports();
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
@@ -2311,9 +2296,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
// Extract information about the script using the mono class.
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
- bool tool = false;
- bool global_class = false;
- bool abstract_class = false;
+ TypeInfo type_info;
// TODO: Use GDExtension godot_dictionary
Array methods_array;
@@ -2323,18 +2306,12 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Dictionary signals_dict;
signals_dict.~Dictionary();
- String class_name;
- String icon_path;
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path,
+ p_script.ptr(), &type_info,
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);
- p_script->class_name = class_name;
- p_script->tool = tool;
- p_script->global_class = global_class;
- p_script->abstract_class = abstract_class;
- p_script->icon_path = icon_path;
+ p_script->type_info = type_info;
p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict;
@@ -2354,6 +2331,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
MethodInfo mi;
mi.name = name;
+ mi.return_val = PropertyInfo::from_dict(method_info_dict["return_val"]);
+
Array params = method_info_dict["params"];
for (int j = 0; j < params.size(); j++) {
@@ -2411,7 +2390,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool CSharpScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
- bool extra_cond = tool || ScriptServer::is_scripting_enabled();
+ bool extra_cond = type_info.is_tool || ScriptServer::is_scripting_enabled();
#else
bool extra_cond = true;
#endif
@@ -2420,10 +2399,10 @@ bool CSharpScript::can_instantiate() const {
// For tool scripts, this will never fire if the class is not found. That's because we
// don't know if it's a tool script if we can't find the class to access the attributes.
if (extra_cond && !valid) {
- ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
+ ERR_FAIL_V_MSG(false, "Cannot instantiate C# script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
}
- return valid && !abstract_class && extra_cond;
+ return valid && type_info.can_instantiate() && extra_cond;
}
StringName CSharpScript::get_instance_base_type() const {
@@ -2433,6 +2412,8 @@ StringName CSharpScript::get_instance_base_type() const {
}
CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {
+ ERR_FAIL_COND_V_MSG(!type_info.can_instantiate(), nullptr, "Cannot instantiate C# script. Script: '" + get_path() + "'.");
+
/* STEP 1, CREATE */
Ref<RefCounted> ref;
@@ -2653,7 +2634,7 @@ Error CSharpScript::reload(bool p_keep_state) {
_update_exports();
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
@@ -2747,11 +2728,11 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
}
Ref<Script> CSharpScript::get_base_script() const {
- return base_script.is_valid() && !base_script->get_path().is_empty() ? base_script : nullptr;
+ return base_script;
}
StringName CSharpScript::get_global_name() const {
- return global_class ? StringName(class_name) : StringName();
+ return type_info.is_global_class ? StringName(type_info.class_name) : StringName();
}
void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
@@ -2808,7 +2789,7 @@ Error CSharpScript::load_source_code(const String &p_path) {
}
void CSharpScript::_clear() {
- tool = false;
+ type_info = TypeInfo();
valid = false;
reload_invalidated = true;
}
@@ -2819,15 +2800,17 @@ CSharpScript::CSharpScript() {
#ifdef DEBUG_ENABLED
{
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
- CSharpLanguage::get_singleton()->script_list.add(&this->script_list);
+ CSharpLanguage::get_singleton()->script_list.add(&script_list);
}
#endif
}
CSharpScript::~CSharpScript() {
#ifdef DEBUG_ENABLED
- MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
- CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
+ {
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
+ CSharpLanguage::get_singleton()->script_list.remove(&script_list);
+ }
#endif
if (GDMonoCache::godot_api_cache_updated) {
@@ -2854,21 +2837,32 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
// TODO ignore anything inside bin/ and obj/ in tools builds?
+ String real_path = p_path;
+ if (p_path.begins_with("csharp://")) {
+ // This is a virtual path used by generic types, extract the real path.
+ real_path = "res://" + p_path.trim_prefix("csharp://");
+ real_path = real_path.substr(0, real_path.rfind(":"));
+ }
+
Ref<CSharpScript> scr;
if (GDMonoCache::godot_api_cache_updated) {
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
+ ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
} else {
scr = Ref<CSharpScript>(memnew(CSharpScript));
}
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
- Error err = scr->load_source_code(p_path);
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'.");
+ Error err = scr->load_source_code(real_path);
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + real_path + "'.");
#endif
- scr->set_path(p_original_path);
+ // Only one instance of a C# script is allowed to exist.
+ ERR_FAIL_COND_V_MSG(!scr->get_path().is_empty() && scr->get_path() != p_original_path, Ref<Resource>(),
+ "The C# script path is different from the path it was registered in the C# dictionary.");
+ scr->set_path(p_original_path, true);
scr->reload();
if (r_error) {
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 401f5c62e5..14a9dc2850 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -61,12 +61,87 @@ class CSharpScript : public Script {
friend class CSharpInstance;
friend class CSharpLanguage;
- bool tool = false;
- bool global_class = false;
- bool abstract_class = false;
+public:
+ struct TypeInfo {
+ /**
+ * Name of the C# class.
+ */
+ String class_name;
+
+ /**
+ * Path to the icon that will be used for this class by the editor.
+ */
+ String icon_path;
+
+ /**
+ * Script is marked as tool and runs in the editor.
+ */
+ bool is_tool = false;
+
+ /**
+ * Script is marked as global class and will be registered in the editor.
+ * Registered classes can be created using certain editor dialogs and
+ * can be referenced by name from other languages that support the feature.
+ */
+ bool is_global_class = false;
+
+ /**
+ * Script is declared abstract.
+ */
+ bool is_abstract = false;
+
+ /**
+ * The C# type that corresponds to this script is a constructed generic type.
+ * E.g.: `Dictionary<int, string>`
+ */
+ bool is_constructed_generic_type = false;
+
+ /**
+ * The C# type that corresponds to this script is a generic type definition.
+ * E.g.: `Dictionary<,>`
+ */
+ bool is_generic_type_definition = false;
+
+ /**
+ * The C# type that corresponds to this script contains generic type parameters,
+ * regardless of whether the type parameters are bound or not.
+ */
+ bool is_generic() const {
+ return is_constructed_generic_type || is_generic_type_definition;
+ }
+
+ /**
+ * Check if the script can be instantiated.
+ * C# types can't be instantiated if they are abstract or contain generic
+ * type parameters, but a CSharpScript is still created for them.
+ */
+ bool can_instantiate() const {
+ return !is_abstract && !is_generic_type_definition;
+ }
+ };
+
+private:
+ /**
+ * Contains the C# type information for this script.
+ */
+ TypeInfo type_info;
+
+ /**
+ * Scripts are valid when the corresponding C# class is found and used
+ * to extract the script info using the [update_script_class_info] method.
+ */
bool valid = false;
+ /**
+ * Scripts extract info from the C# class in the reload methods but,
+ * if the reload is not invalidated, then the current extracted info
+ * is still valid and there's no need to reload again.
+ */
bool reload_invalidated = false;
+ /**
+ * Base script that this script derives from, or null if it derives from a
+ * native Godot class.
+ */
Ref<CSharpScript> base_script;
HashSet<Object *> instances;
@@ -87,9 +162,10 @@ class CSharpScript : public Script {
HashSet<ObjectID> pending_replace_placeholders;
#endif
+ /**
+ * Script source code.
+ */
String source;
- String class_name;
- String icon_path;
SelfList<CSharpScript> script_list = this;
@@ -166,7 +242,7 @@ public:
return docs;
}
virtual String get_class_icon_path() const override {
- return icon_path;
+ return type_info.icon_path;
}
#endif // TOOLS_ENABLED
@@ -184,13 +260,13 @@ public:
void get_members(HashSet<StringName> *p_members) override;
bool is_tool() const override {
- return tool;
+ return type_info.is_tool;
}
bool is_valid() const override {
return valid;
}
bool is_abstract() const override {
- return abstract_class;
+ return type_info.is_abstract;
}
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -423,7 +499,7 @@ public:
void get_string_delimiters(List<String> *p_delimiters) const override;
bool is_using_templates() override;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
- virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override;
+ virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override;
/* TODO */ bool validate(const String &p_script, const String &p_path, List<String> *r_functions,
List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override {
return true;
@@ -478,6 +554,7 @@ public:
/* TODO? */ void get_public_annotations(List<MethodInfo> *p_annotations) const override {}
void reload_all_scripts() override;
+ void reload_scripts(const Array &p_scripts, bool p_soft_reload) override;
void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
/* LOADER FUNCTIONS */
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
index 03a7dc453c..9674626183 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "G
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Tests", "Godot.SourceGenerators.Tests\Godot.SourceGenerators.Tests.csproj", "{07E6D201-35C9-4463-9B29-D16621EA733D}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
EndProject
Global
@@ -26,6 +28,10 @@ Global
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs
new file mode 100644
index 0000000000..b9f11908e1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs
@@ -0,0 +1,14 @@
+namespace Godot.SourceGenerators.Sample;
+
+[GlobalClass]
+public partial class CustomGlobalClass : GodotObject
+{
+}
+
+// This doesn't works because global classes can't have any generic type parameter.
+/*
+[GlobalClass]
+public partial class CustomGlobalClass<T> : Node
+{
+}
+*/
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
index 3f569ebac3..d0907c1cd4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
+ <LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs
new file mode 100644
index 0000000000..f19da77be6
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs
@@ -0,0 +1,654 @@
+using System;
+using Godot.Collections;
+using Array = Godot.Collections.Array;
+
+namespace Godot.SourceGenerators.Sample;
+
+public class MustBeVariantMethods
+{
+ public void MustBeVariantMethodCalls()
+ {
+ Method<bool>();
+ Method<char>();
+ Method<sbyte>();
+ Method<byte>();
+ Method<short>();
+ Method<ushort>();
+ Method<int>();
+ Method<uint>();
+ Method<long>();
+ Method<ulong>();
+ Method<float>();
+ Method<double>();
+ Method<string>();
+ Method<Vector2>();
+ Method<Vector2I>();
+ Method<Rect2>();
+ Method<Rect2I>();
+ Method<Transform2D>();
+ Method<Vector3>();
+ Method<Vector3I>();
+ Method<Vector4>();
+ Method<Vector4I>();
+ Method<Basis>();
+ Method<Quaternion>();
+ Method<Transform3D>();
+ Method<Projection>();
+ Method<Aabb>();
+ Method<Color>();
+ Method<Plane>();
+ Method<Callable>();
+ Method<Signal>();
+ Method<GodotObject>();
+ Method<StringName>();
+ Method<NodePath>();
+ Method<Rid>();
+ Method<Dictionary>();
+ Method<Array>();
+ Method<byte[]>();
+ Method<int[]>();
+ Method<long[]>();
+ Method<float[]>();
+ Method<double[]>();
+ Method<string[]>();
+ Method<Vector2[]>();
+ Method<Vector3[]>();
+ Method<Color[]>();
+ Method<GodotObject[]>();
+ Method<StringName[]>();
+ Method<NodePath[]>();
+ Method<Rid[]>();
+
+ // This call fails because generic type is not Variant-compatible.
+ //Method<object>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+
+ public void MustBeVariantClasses()
+ {
+ new ClassWithGenericVariant<bool>();
+ new ClassWithGenericVariant<char>();
+ new ClassWithGenericVariant<sbyte>();
+ new ClassWithGenericVariant<byte>();
+ new ClassWithGenericVariant<short>();
+ new ClassWithGenericVariant<ushort>();
+ new ClassWithGenericVariant<int>();
+ new ClassWithGenericVariant<uint>();
+ new ClassWithGenericVariant<long>();
+ new ClassWithGenericVariant<ulong>();
+ new ClassWithGenericVariant<float>();
+ new ClassWithGenericVariant<double>();
+ new ClassWithGenericVariant<string>();
+ new ClassWithGenericVariant<Vector2>();
+ new ClassWithGenericVariant<Vector2I>();
+ new ClassWithGenericVariant<Rect2>();
+ new ClassWithGenericVariant<Rect2I>();
+ new ClassWithGenericVariant<Transform2D>();
+ new ClassWithGenericVariant<Vector3>();
+ new ClassWithGenericVariant<Vector3I>();
+ new ClassWithGenericVariant<Vector4>();
+ new ClassWithGenericVariant<Vector4I>();
+ new ClassWithGenericVariant<Basis>();
+ new ClassWithGenericVariant<Quaternion>();
+ new ClassWithGenericVariant<Transform3D>();
+ new ClassWithGenericVariant<Projection>();
+ new ClassWithGenericVariant<Aabb>();
+ new ClassWithGenericVariant<Color>();
+ new ClassWithGenericVariant<Plane>();
+ new ClassWithGenericVariant<Callable>();
+ new ClassWithGenericVariant<Signal>();
+ new ClassWithGenericVariant<GodotObject>();
+ new ClassWithGenericVariant<StringName>();
+ new ClassWithGenericVariant<NodePath>();
+ new ClassWithGenericVariant<Rid>();
+ new ClassWithGenericVariant<Dictionary>();
+ new ClassWithGenericVariant<Array>();
+ new ClassWithGenericVariant<byte[]>();
+ new ClassWithGenericVariant<int[]>();
+ new ClassWithGenericVariant<long[]>();
+ new ClassWithGenericVariant<float[]>();
+ new ClassWithGenericVariant<double[]>();
+ new ClassWithGenericVariant<string[]>();
+ new ClassWithGenericVariant<Vector2[]>();
+ new ClassWithGenericVariant<Vector3[]>();
+ new ClassWithGenericVariant<Color[]>();
+ new ClassWithGenericVariant<GodotObject[]>();
+ new ClassWithGenericVariant<StringName[]>();
+ new ClassWithGenericVariant<NodePath[]>();
+ new ClassWithGenericVariant<Rid[]>();
+
+ // This class fails because generic type is not Variant-compatible.
+ //new ClassWithGenericVariant<object>();
+ }
+}
+
+public class ClassWithGenericVariant<[MustBeVariant] T>
+{
+}
+
+public class MustBeVariantAnnotatedMethods
+{
+ [GenericTypeAttribute<bool>()]
+ public void MethodWithAttributeBool()
+ {
+ }
+
+ [GenericTypeAttribute<char>()]
+ public void MethodWithAttributeChar()
+ {
+ }
+
+ [GenericTypeAttribute<sbyte>()]
+ public void MethodWithAttributeSByte()
+ {
+ }
+
+ [GenericTypeAttribute<byte>()]
+ public void MethodWithAttributeByte()
+ {
+ }
+
+ [GenericTypeAttribute<short>()]
+ public void MethodWithAttributeInt16()
+ {
+ }
+
+ [GenericTypeAttribute<ushort>()]
+ public void MethodWithAttributeUInt16()
+ {
+ }
+
+ [GenericTypeAttribute<int>()]
+ public void MethodWithAttributeInt32()
+ {
+ }
+
+ [GenericTypeAttribute<uint>()]
+ public void MethodWithAttributeUInt32()
+ {
+ }
+
+ [GenericTypeAttribute<long>()]
+ public void MethodWithAttributeInt64()
+ {
+ }
+
+ [GenericTypeAttribute<ulong>()]
+ public void MethodWithAttributeUInt64()
+ {
+ }
+
+ [GenericTypeAttribute<float>()]
+ public void MethodWithAttributeSingle()
+ {
+ }
+
+ [GenericTypeAttribute<double>()]
+ public void MethodWithAttributeDouble()
+ {
+ }
+
+ [GenericTypeAttribute<string>()]
+ public void MethodWithAttributeString()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2>()]
+ public void MethodWithAttributeVector2()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2I>()]
+ public void MethodWithAttributeVector2I()
+ {
+ }
+
+ [GenericTypeAttribute<Rect2>()]
+ public void MethodWithAttributeRect2()
+ {
+ }
+
+ [GenericTypeAttribute<Rect2I>()]
+ public void MethodWithAttributeRect2I()
+ {
+ }
+
+ [GenericTypeAttribute<Transform2D>()]
+ public void MethodWithAttributeTransform2D()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3>()]
+ public void MethodWithAttributeVector3()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3I>()]
+ public void MethodWithAttributeVector3I()
+ {
+ }
+
+ [GenericTypeAttribute<Vector4>()]
+ public void MethodWithAttributeVector4()
+ {
+ }
+
+ [GenericTypeAttribute<Vector4I>()]
+ public void MethodWithAttributeVector4I()
+ {
+ }
+
+ [GenericTypeAttribute<Basis>()]
+ public void MethodWithAttributeBasis()
+ {
+ }
+
+ [GenericTypeAttribute<Quaternion>()]
+ public void MethodWithAttributeQuaternion()
+ {
+ }
+
+ [GenericTypeAttribute<Transform3D>()]
+ public void MethodWithAttributeTransform3D()
+ {
+ }
+
+ [GenericTypeAttribute<Projection>()]
+ public void MethodWithAttributeProjection()
+ {
+ }
+
+ [GenericTypeAttribute<Aabb>()]
+ public void MethodWithAttributeAabb()
+ {
+ }
+
+ [GenericTypeAttribute<Color>()]
+ public void MethodWithAttributeColor()
+ {
+ }
+
+ [GenericTypeAttribute<Plane>()]
+ public void MethodWithAttributePlane()
+ {
+ }
+
+ [GenericTypeAttribute<Callable>()]
+ public void MethodWithAttributeCallable()
+ {
+ }
+
+ [GenericTypeAttribute<Signal>()]
+ public void MethodWithAttributeSignal()
+ {
+ }
+
+ [GenericTypeAttribute<GodotObject>()]
+ public void MethodWithAttributeGodotObject()
+ {
+ }
+
+ [GenericTypeAttribute<StringName>()]
+ public void MethodWithAttributeStringName()
+ {
+ }
+
+ [GenericTypeAttribute<NodePath>()]
+ public void MethodWithAttributeNodePath()
+ {
+ }
+
+ [GenericTypeAttribute<Rid>()]
+ public void MethodWithAttributeRid()
+ {
+ }
+
+ [GenericTypeAttribute<Dictionary>()]
+ public void MethodWithAttributeDictionary()
+ {
+ }
+
+ [GenericTypeAttribute<Array>()]
+ public void MethodWithAttributeArray()
+ {
+ }
+
+ [GenericTypeAttribute<byte[]>()]
+ public void MethodWithAttributeByteArray()
+ {
+ }
+
+ [GenericTypeAttribute<int[]>()]
+ public void MethodWithAttributeInt32Array()
+ {
+ }
+
+ [GenericTypeAttribute<long[]>()]
+ public void MethodWithAttributeInt64Array()
+ {
+ }
+
+ [GenericTypeAttribute<float[]>()]
+ public void MethodWithAttributeSingleArray()
+ {
+ }
+
+ [GenericTypeAttribute<double[]>()]
+ public void MethodWithAttributeDoubleArray()
+ {
+ }
+
+ [GenericTypeAttribute<string[]>()]
+ public void MethodWithAttributeStringArray()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2[]>()]
+ public void MethodWithAttributeVector2Array()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3[]>()]
+ public void MethodWithAttributeVector3Array()
+ {
+ }
+
+ [GenericTypeAttribute<Color[]>()]
+ public void MethodWithAttributeColorArray()
+ {
+ }
+
+ [GenericTypeAttribute<GodotObject[]>()]
+ public void MethodWithAttributeGodotObjectArray()
+ {
+ }
+
+ [GenericTypeAttribute<StringName[]>()]
+ public void MethodWithAttributeStringNameArray()
+ {
+ }
+
+ [GenericTypeAttribute<NodePath[]>()]
+ public void MethodWithAttributeNodePathArray()
+ {
+ }
+
+ [GenericTypeAttribute<Rid[]>()]
+ public void MethodWithAttributeRidArray()
+ {
+ }
+
+ // This method definition fails because generic type is not Variant-compatible.
+ /*
+ [GenericTypeAttribute<object>()]
+ public void MethodWithWrongAttribute()
+ {
+ }
+ */
+}
+
+[GenericTypeAttribute<bool>()]
+public class ClassVariantAnnotatedBool
+{
+}
+
+[GenericTypeAttribute<char>()]
+public class ClassVariantAnnotatedChar
+{
+}
+
+[GenericTypeAttribute<sbyte>()]
+public class ClassVariantAnnotatedSByte
+{
+}
+
+[GenericTypeAttribute<byte>()]
+public class ClassVariantAnnotatedByte
+{
+}
+
+[GenericTypeAttribute<short>()]
+public class ClassVariantAnnotatedInt16
+{
+}
+
+[GenericTypeAttribute<ushort>()]
+public class ClassVariantAnnotatedUInt16
+{
+}
+
+[GenericTypeAttribute<int>()]
+public class ClassVariantAnnotatedInt32
+{
+}
+
+[GenericTypeAttribute<uint>()]
+public class ClassVariantAnnotatedUInt32
+{
+}
+
+[GenericTypeAttribute<long>()]
+public class ClassVariantAnnotatedInt64
+{
+}
+
+[GenericTypeAttribute<ulong>()]
+public class ClassVariantAnnotatedUInt64
+{
+}
+
+[GenericTypeAttribute<float>()]
+public class ClassVariantAnnotatedSingle
+{
+}
+
+[GenericTypeAttribute<double>()]
+public class ClassVariantAnnotatedDouble
+{
+}
+
+[GenericTypeAttribute<string>()]
+public class ClassVariantAnnotatedString
+{
+}
+
+[GenericTypeAttribute<Vector2>()]
+public class ClassVariantAnnotatedVector2
+{
+}
+
+[GenericTypeAttribute<Vector2I>()]
+public class ClassVariantAnnotatedVector2I
+{
+}
+
+[GenericTypeAttribute<Rect2>()]
+public class ClassVariantAnnotatedRect2
+{
+}
+
+[GenericTypeAttribute<Rect2I>()]
+public class ClassVariantAnnotatedRect2I
+{
+}
+
+[GenericTypeAttribute<Transform2D>()]
+public class ClassVariantAnnotatedTransform2D
+{
+}
+
+[GenericTypeAttribute<Vector3>()]
+public class ClassVariantAnnotatedVector3
+{
+}
+
+[GenericTypeAttribute<Vector3I>()]
+public class ClassVariantAnnotatedVector3I
+{
+}
+
+[GenericTypeAttribute<Vector4>()]
+public class ClassVariantAnnotatedVector4
+{
+}
+
+[GenericTypeAttribute<Vector4I>()]
+public class ClassVariantAnnotatedVector4I
+{
+}
+
+[GenericTypeAttribute<Basis>()]
+public class ClassVariantAnnotatedBasis
+{
+}
+
+[GenericTypeAttribute<Quaternion>()]
+public class ClassVariantAnnotatedQuaternion
+{
+}
+
+[GenericTypeAttribute<Transform3D>()]
+public class ClassVariantAnnotatedTransform3D
+{
+}
+
+[GenericTypeAttribute<Projection>()]
+public class ClassVariantAnnotatedProjection
+{
+}
+
+[GenericTypeAttribute<Aabb>()]
+public class ClassVariantAnnotatedAabb
+{
+}
+
+[GenericTypeAttribute<Color>()]
+public class ClassVariantAnnotatedColor
+{
+}
+
+[GenericTypeAttribute<Plane>()]
+public class ClassVariantAnnotatedPlane
+{
+}
+
+[GenericTypeAttribute<Callable>()]
+public class ClassVariantAnnotatedCallable
+{
+}
+
+[GenericTypeAttribute<Signal>()]
+public class ClassVariantAnnotatedSignal
+{
+}
+
+[GenericTypeAttribute<GodotObject>()]
+public class ClassVariantAnnotatedGodotObject
+{
+}
+
+[GenericTypeAttribute<StringName>()]
+public class ClassVariantAnnotatedStringName
+{
+}
+
+[GenericTypeAttribute<NodePath>()]
+public class ClassVariantAnnotatedNodePath
+{
+}
+
+[GenericTypeAttribute<Rid>()]
+public class ClassVariantAnnotatedRid
+{
+}
+
+[GenericTypeAttribute<Dictionary>()]
+public class ClassVariantAnnotatedDictionary
+{
+}
+
+[GenericTypeAttribute<Array>()]
+public class ClassVariantAnnotatedArray
+{
+}
+
+[GenericTypeAttribute<byte[]>()]
+public class ClassVariantAnnotatedByteArray
+{
+}
+
+[GenericTypeAttribute<int[]>()]
+public class ClassVariantAnnotatedInt32Array
+{
+}
+
+[GenericTypeAttribute<long[]>()]
+public class ClassVariantAnnotatedInt64Array
+{
+}
+
+[GenericTypeAttribute<float[]>()]
+public class ClassVariantAnnotatedSingleArray
+{
+}
+
+[GenericTypeAttribute<double[]>()]
+public class ClassVariantAnnotatedDoubleArray
+{
+}
+
+[GenericTypeAttribute<string[]>()]
+public class ClassVariantAnnotatedStringArray
+{
+}
+
+[GenericTypeAttribute<Vector2[]>()]
+public class ClassVariantAnnotatedVector2Array
+{
+}
+
+[GenericTypeAttribute<Vector3[]>()]
+public class ClassVariantAnnotatedVector3Array
+{
+}
+
+[GenericTypeAttribute<Color[]>()]
+public class ClassVariantAnnotatedColorArray
+{
+}
+
+[GenericTypeAttribute<GodotObject[]>()]
+public class ClassVariantAnnotatedGodotObjectArray
+{
+}
+
+[GenericTypeAttribute<StringName[]>()]
+public class ClassVariantAnnotatedStringNameArray
+{
+}
+
+[GenericTypeAttribute<NodePath[]>()]
+public class ClassVariantAnnotatedNodePathArray
+{
+}
+
+[GenericTypeAttribute<Rid[]>()]
+public class ClassVariantAnnotatedRidArray
+{
+}
+
+// This class definition fails because generic type is not Variant-compatible.
+/*
+[GenericTypeAttribute<object>()]
+public class ClassNonVariantAnnotated
+{
+}
+*/
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs
new file mode 100644
index 0000000000..253889296b
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators.Tests;
+
+public static class CSharpAnalyzerVerifier<TAnalyzer>
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ public const LanguageVersion LangVersion = LanguageVersion.CSharp11;
+
+ public class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier>
+ {
+ public Test()
+ {
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+
+ SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
+ {
+ Project project =
+ solution.GetProject(projectId)!.AddMetadataReference(Constants.GodotSharpAssembly
+ .CreateMetadataReference()).WithParseOptions(new CSharpParseOptions(LangVersion));
+
+ return project.Solution;
+ });
+ }
+ }
+
+ public static Task Verify(string sources, params DiagnosticResult[] expected)
+ {
+ return MakeVerifier(new string[] { sources }, expected).RunAsync();
+ }
+
+ public static Test MakeVerifier(ICollection<string> sources, params DiagnosticResult[] expected)
+ {
+ var verifier = new Test();
+
+ verifier.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", $"""
+ is_global = true
+ build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath}
+ """));
+
+ verifier.TestState.Sources.AddRange(sources.Select(source =>
+ {
+ return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source))));
+ }));
+
+ verifier.ExpectedDiagnostics.AddRange(expected);
+ return verifier;
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs
new file mode 100644
index 0000000000..74d6afceb3
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs
@@ -0,0 +1,20 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class GlobalClassAnalyzerTests
+{
+ [Fact]
+ public async void GlobalClassMustDeriveFromGodotObjectTest()
+ {
+ const string GlobalClassGD0401 = "GlobalClass.GD0401.cs";
+ await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0401);
+ }
+
+ [Fact]
+ public async void GlobalClassMustNotBeGenericTest()
+ {
+ const string GlobalClassGD0402 = "GlobalClass.GD0402.cs";
+ await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0402);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
index e39c14f049..32d7ca7486 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
@@ -15,8 +15,11 @@
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="1.1.1" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs
new file mode 100644
index 0000000000..62c602efbb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs
@@ -0,0 +1,20 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class MustBeVariantAnalyzerTests
+{
+ [Fact]
+ public async void GenericTypeArgumentMustBeVariantTest()
+ {
+ const string MustBeVariantGD0301 = "MustBeVariant.GD0301.cs";
+ await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0301);
+ }
+
+ [Fact]
+ public async void GenericTypeParameterMustBeVariantAnnotatedTest()
+ {
+ const string MustBeVariantGD0302 = "MustBeVariant.GD0302.cs";
+ await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0302);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs
new file mode 100644
index 0000000000..6e6d3a6f39
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs
@@ -0,0 +1,22 @@
+using Godot;
+
+// This works because it inherits from GodotObject.
+[GlobalClass]
+public partial class CustomGlobalClass1 : GodotObject
+{
+
+}
+
+// This works because it inherits from an object that inherits from GodotObject
+[GlobalClass]
+public partial class CustomGlobalClass2 : Node
+{
+
+}
+
+// This raises a GD0401 diagnostic error: global classes must inherit from GodotObject
+{|GD0401:[GlobalClass]
+public partial class CustomGlobalClass3
+{
+
+}|}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs
new file mode 100644
index 0000000000..1c0a169841
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs
@@ -0,0 +1,15 @@
+using Godot;
+
+// This works because it inherits from GodotObject and it doesn't have any generic type parameter.
+[GlobalClass]
+public partial class CustomGlobalClass : GodotObject
+{
+
+}
+
+// This raises a GD0402 diagnostic error: global classes can't have any generic type parameter
+{|GD0402:[GlobalClass]
+public partial class CustomGlobalClass<T> : GodotObject
+{
+
+}|}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
new file mode 100644
index 0000000000..462da31d66
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
@@ -0,0 +1,653 @@
+using System;
+using Godot;
+using Godot.Collections;
+using Array = Godot.Collections.Array;
+
+public class MustBeVariantGD0301
+{
+ public void MethodCallsError()
+ {
+ // This raises a GD0301 diagnostic error: object is not Variant (and Method<T> requires a variant generic type).
+ Method<{|GD0301:object|}>();
+ }
+
+ public void MethodCallsOk()
+ {
+ // All these calls are valid because they are Variant types.
+ Method<bool>();
+ Method<char>();
+ Method<sbyte>();
+ Method<byte>();
+ Method<short>();
+ Method<ushort>();
+ Method<int>();
+ Method<uint>();
+ Method<long>();
+ Method<ulong>();
+ Method<float>();
+ Method<double>();
+ Method<string>();
+ Method<Vector2>();
+ Method<Vector2I>();
+ Method<Rect2>();
+ Method<Rect2I>();
+ Method<Transform2D>();
+ Method<Vector3>();
+ Method<Vector3I>();
+ Method<Vector4>();
+ Method<Vector4I>();
+ Method<Basis>();
+ Method<Quaternion>();
+ Method<Transform3D>();
+ Method<Projection>();
+ Method<Aabb>();
+ Method<Color>();
+ Method<Plane>();
+ Method<Callable>();
+ Method<Signal>();
+ Method<GodotObject>();
+ Method<StringName>();
+ Method<NodePath>();
+ Method<Rid>();
+ Method<Dictionary>();
+ Method<Array>();
+ Method<byte[]>();
+ Method<int[]>();
+ Method<long[]>();
+ Method<float[]>();
+ Method<double[]>();
+ Method<string[]>();
+ Method<Vector2[]>();
+ Method<Vector3[]>();
+ Method<Color[]>();
+ Method<GodotObject[]>();
+ Method<StringName[]>();
+ Method<NodePath[]>();
+ Method<Rid[]>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+
+ public void MustBeVariantClasses()
+ {
+ new ClassWithGenericVariant<bool>();
+ new ClassWithGenericVariant<char>();
+ new ClassWithGenericVariant<sbyte>();
+ new ClassWithGenericVariant<byte>();
+ new ClassWithGenericVariant<short>();
+ new ClassWithGenericVariant<ushort>();
+ new ClassWithGenericVariant<int>();
+ new ClassWithGenericVariant<uint>();
+ new ClassWithGenericVariant<long>();
+ new ClassWithGenericVariant<ulong>();
+ new ClassWithGenericVariant<float>();
+ new ClassWithGenericVariant<double>();
+ new ClassWithGenericVariant<string>();
+ new ClassWithGenericVariant<Vector2>();
+ new ClassWithGenericVariant<Vector2I>();
+ new ClassWithGenericVariant<Rect2>();
+ new ClassWithGenericVariant<Rect2I>();
+ new ClassWithGenericVariant<Transform2D>();
+ new ClassWithGenericVariant<Vector3>();
+ new ClassWithGenericVariant<Vector3I>();
+ new ClassWithGenericVariant<Vector4>();
+ new ClassWithGenericVariant<Vector4I>();
+ new ClassWithGenericVariant<Basis>();
+ new ClassWithGenericVariant<Quaternion>();
+ new ClassWithGenericVariant<Transform3D>();
+ new ClassWithGenericVariant<Projection>();
+ new ClassWithGenericVariant<Aabb>();
+ new ClassWithGenericVariant<Color>();
+ new ClassWithGenericVariant<Plane>();
+ new ClassWithGenericVariant<Callable>();
+ new ClassWithGenericVariant<Signal>();
+ new ClassWithGenericVariant<GodotObject>();
+ new ClassWithGenericVariant<StringName>();
+ new ClassWithGenericVariant<NodePath>();
+ new ClassWithGenericVariant<Rid>();
+ new ClassWithGenericVariant<Dictionary>();
+ new ClassWithGenericVariant<Array>();
+ new ClassWithGenericVariant<byte[]>();
+ new ClassWithGenericVariant<int[]>();
+ new ClassWithGenericVariant<long[]>();
+ new ClassWithGenericVariant<float[]>();
+ new ClassWithGenericVariant<double[]>();
+ new ClassWithGenericVariant<string[]>();
+ new ClassWithGenericVariant<Vector2[]>();
+ new ClassWithGenericVariant<Vector3[]>();
+ new ClassWithGenericVariant<Color[]>();
+ new ClassWithGenericVariant<GodotObject[]>();
+ new ClassWithGenericVariant<StringName[]>();
+ new ClassWithGenericVariant<NodePath[]>();
+ new ClassWithGenericVariant<Rid[]>();
+
+ // This class fails because generic type is not Variant-compatible.
+ new ClassWithGenericVariant<{|GD0301:object|}>();
+ }
+}
+
+public class ClassWithGenericVariant<[MustBeVariant] T>
+{
+}
+
+public class MustBeVariantAnnotatedMethods
+{
+ [GenericTypeAttribute<bool>()]
+ public void MethodWithAttributeBool()
+ {
+ }
+
+ [GenericTypeAttribute<char>()]
+ public void MethodWithAttributeChar()
+ {
+ }
+
+ [GenericTypeAttribute<sbyte>()]
+ public void MethodWithAttributeSByte()
+ {
+ }
+
+ [GenericTypeAttribute<byte>()]
+ public void MethodWithAttributeByte()
+ {
+ }
+
+ [GenericTypeAttribute<short>()]
+ public void MethodWithAttributeInt16()
+ {
+ }
+
+ [GenericTypeAttribute<ushort>()]
+ public void MethodWithAttributeUInt16()
+ {
+ }
+
+ [GenericTypeAttribute<int>()]
+ public void MethodWithAttributeInt32()
+ {
+ }
+
+ [GenericTypeAttribute<uint>()]
+ public void MethodWithAttributeUInt32()
+ {
+ }
+
+ [GenericTypeAttribute<long>()]
+ public void MethodWithAttributeInt64()
+ {
+ }
+
+ [GenericTypeAttribute<ulong>()]
+ public void MethodWithAttributeUInt64()
+ {
+ }
+
+ [GenericTypeAttribute<float>()]
+ public void MethodWithAttributeSingle()
+ {
+ }
+
+ [GenericTypeAttribute<double>()]
+ public void MethodWithAttributeDouble()
+ {
+ }
+
+ [GenericTypeAttribute<string>()]
+ public void MethodWithAttributeString()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2>()]
+ public void MethodWithAttributeVector2()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2I>()]
+ public void MethodWithAttributeVector2I()
+ {
+ }
+
+ [GenericTypeAttribute<Rect2>()]
+ public void MethodWithAttributeRect2()
+ {
+ }
+
+ [GenericTypeAttribute<Rect2I>()]
+ public void MethodWithAttributeRect2I()
+ {
+ }
+
+ [GenericTypeAttribute<Transform2D>()]
+ public void MethodWithAttributeTransform2D()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3>()]
+ public void MethodWithAttributeVector3()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3I>()]
+ public void MethodWithAttributeVector3I()
+ {
+ }
+
+ [GenericTypeAttribute<Vector4>()]
+ public void MethodWithAttributeVector4()
+ {
+ }
+
+ [GenericTypeAttribute<Vector4I>()]
+ public void MethodWithAttributeVector4I()
+ {
+ }
+
+ [GenericTypeAttribute<Basis>()]
+ public void MethodWithAttributeBasis()
+ {
+ }
+
+ [GenericTypeAttribute<Quaternion>()]
+ public void MethodWithAttributeQuaternion()
+ {
+ }
+
+ [GenericTypeAttribute<Transform3D>()]
+ public void MethodWithAttributeTransform3D()
+ {
+ }
+
+ [GenericTypeAttribute<Projection>()]
+ public void MethodWithAttributeProjection()
+ {
+ }
+
+ [GenericTypeAttribute<Aabb>()]
+ public void MethodWithAttributeAabb()
+ {
+ }
+
+ [GenericTypeAttribute<Color>()]
+ public void MethodWithAttributeColor()
+ {
+ }
+
+ [GenericTypeAttribute<Plane>()]
+ public void MethodWithAttributePlane()
+ {
+ }
+
+ [GenericTypeAttribute<Callable>()]
+ public void MethodWithAttributeCallable()
+ {
+ }
+
+ [GenericTypeAttribute<Signal>()]
+ public void MethodWithAttributeSignal()
+ {
+ }
+
+ [GenericTypeAttribute<GodotObject>()]
+ public void MethodWithAttributeGodotObject()
+ {
+ }
+
+ [GenericTypeAttribute<StringName>()]
+ public void MethodWithAttributeStringName()
+ {
+ }
+
+ [GenericTypeAttribute<NodePath>()]
+ public void MethodWithAttributeNodePath()
+ {
+ }
+
+ [GenericTypeAttribute<Rid>()]
+ public void MethodWithAttributeRid()
+ {
+ }
+
+ [GenericTypeAttribute<Dictionary>()]
+ public void MethodWithAttributeDictionary()
+ {
+ }
+
+ [GenericTypeAttribute<Array>()]
+ public void MethodWithAttributeArray()
+ {
+ }
+
+ [GenericTypeAttribute<byte[]>()]
+ public void MethodWithAttributeByteArray()
+ {
+ }
+
+ [GenericTypeAttribute<int[]>()]
+ public void MethodWithAttributeInt32Array()
+ {
+ }
+
+ [GenericTypeAttribute<long[]>()]
+ public void MethodWithAttributeInt64Array()
+ {
+ }
+
+ [GenericTypeAttribute<float[]>()]
+ public void MethodWithAttributeSingleArray()
+ {
+ }
+
+ [GenericTypeAttribute<double[]>()]
+ public void MethodWithAttributeDoubleArray()
+ {
+ }
+
+ [GenericTypeAttribute<string[]>()]
+ public void MethodWithAttributeStringArray()
+ {
+ }
+
+ [GenericTypeAttribute<Vector2[]>()]
+ public void MethodWithAttributeVector2Array()
+ {
+ }
+
+ [GenericTypeAttribute<Vector3[]>()]
+ public void MethodWithAttributeVector3Array()
+ {
+ }
+
+ [GenericTypeAttribute<Color[]>()]
+ public void MethodWithAttributeColorArray()
+ {
+ }
+
+ [GenericTypeAttribute<GodotObject[]>()]
+ public void MethodWithAttributeGodotObjectArray()
+ {
+ }
+
+ [GenericTypeAttribute<StringName[]>()]
+ public void MethodWithAttributeStringNameArray()
+ {
+ }
+
+ [GenericTypeAttribute<NodePath[]>()]
+ public void MethodWithAttributeNodePathArray()
+ {
+ }
+
+ [GenericTypeAttribute<Rid[]>()]
+ public void MethodWithAttributeRidArray()
+ {
+ }
+
+ // This method definition fails because generic type is not Variant-compatible.
+ [GenericTypeAttribute<{|GD0301:object|}>()]
+ public void MethodWithWrongAttribute()
+ {
+ }
+}
+
+[GenericTypeAttribute<bool>()]
+public class ClassVariantAnnotatedBool
+{
+}
+
+[GenericTypeAttribute<char>()]
+public class ClassVariantAnnotatedChar
+{
+}
+
+[GenericTypeAttribute<sbyte>()]
+public class ClassVariantAnnotatedSByte
+{
+}
+
+[GenericTypeAttribute<byte>()]
+public class ClassVariantAnnotatedByte
+{
+}
+
+[GenericTypeAttribute<short>()]
+public class ClassVariantAnnotatedInt16
+{
+}
+
+[GenericTypeAttribute<ushort>()]
+public class ClassVariantAnnotatedUInt16
+{
+}
+
+[GenericTypeAttribute<int>()]
+public class ClassVariantAnnotatedInt32
+{
+}
+
+[GenericTypeAttribute<uint>()]
+public class ClassVariantAnnotatedUInt32
+{
+}
+
+[GenericTypeAttribute<long>()]
+public class ClassVariantAnnotatedInt64
+{
+}
+
+[GenericTypeAttribute<ulong>()]
+public class ClassVariantAnnotatedUInt64
+{
+}
+
+[GenericTypeAttribute<float>()]
+public class ClassVariantAnnotatedSingle
+{
+}
+
+[GenericTypeAttribute<double>()]
+public class ClassVariantAnnotatedDouble
+{
+}
+
+[GenericTypeAttribute<string>()]
+public class ClassVariantAnnotatedString
+{
+}
+
+[GenericTypeAttribute<Vector2>()]
+public class ClassVariantAnnotatedVector2
+{
+}
+
+[GenericTypeAttribute<Vector2I>()]
+public class ClassVariantAnnotatedVector2I
+{
+}
+
+[GenericTypeAttribute<Rect2>()]
+public class ClassVariantAnnotatedRect2
+{
+}
+
+[GenericTypeAttribute<Rect2I>()]
+public class ClassVariantAnnotatedRect2I
+{
+}
+
+[GenericTypeAttribute<Transform2D>()]
+public class ClassVariantAnnotatedTransform2D
+{
+}
+
+[GenericTypeAttribute<Vector3>()]
+public class ClassVariantAnnotatedVector3
+{
+}
+
+[GenericTypeAttribute<Vector3I>()]
+public class ClassVariantAnnotatedVector3I
+{
+}
+
+[GenericTypeAttribute<Vector4>()]
+public class ClassVariantAnnotatedVector4
+{
+}
+
+[GenericTypeAttribute<Vector4I>()]
+public class ClassVariantAnnotatedVector4I
+{
+}
+
+[GenericTypeAttribute<Basis>()]
+public class ClassVariantAnnotatedBasis
+{
+}
+
+[GenericTypeAttribute<Quaternion>()]
+public class ClassVariantAnnotatedQuaternion
+{
+}
+
+[GenericTypeAttribute<Transform3D>()]
+public class ClassVariantAnnotatedTransform3D
+{
+}
+
+[GenericTypeAttribute<Projection>()]
+public class ClassVariantAnnotatedProjection
+{
+}
+
+[GenericTypeAttribute<Aabb>()]
+public class ClassVariantAnnotatedAabb
+{
+}
+
+[GenericTypeAttribute<Color>()]
+public class ClassVariantAnnotatedColor
+{
+}
+
+[GenericTypeAttribute<Plane>()]
+public class ClassVariantAnnotatedPlane
+{
+}
+
+[GenericTypeAttribute<Callable>()]
+public class ClassVariantAnnotatedCallable
+{
+}
+
+[GenericTypeAttribute<Signal>()]
+public class ClassVariantAnnotatedSignal
+{
+}
+
+[GenericTypeAttribute<GodotObject>()]
+public class ClassVariantAnnotatedGodotObject
+{
+}
+
+[GenericTypeAttribute<StringName>()]
+public class ClassVariantAnnotatedStringName
+{
+}
+
+[GenericTypeAttribute<NodePath>()]
+public class ClassVariantAnnotatedNodePath
+{
+}
+
+[GenericTypeAttribute<Rid>()]
+public class ClassVariantAnnotatedRid
+{
+}
+
+[GenericTypeAttribute<Dictionary>()]
+public class ClassVariantAnnotatedDictionary
+{
+}
+
+[GenericTypeAttribute<Array>()]
+public class ClassVariantAnnotatedArray
+{
+}
+
+[GenericTypeAttribute<byte[]>()]
+public class ClassVariantAnnotatedByteArray
+{
+}
+
+[GenericTypeAttribute<int[]>()]
+public class ClassVariantAnnotatedInt32Array
+{
+}
+
+[GenericTypeAttribute<long[]>()]
+public class ClassVariantAnnotatedInt64Array
+{
+}
+
+[GenericTypeAttribute<float[]>()]
+public class ClassVariantAnnotatedSingleArray
+{
+}
+
+[GenericTypeAttribute<double[]>()]
+public class ClassVariantAnnotatedDoubleArray
+{
+}
+
+[GenericTypeAttribute<string[]>()]
+public class ClassVariantAnnotatedStringArray
+{
+}
+
+[GenericTypeAttribute<Vector2[]>()]
+public class ClassVariantAnnotatedVector2Array
+{
+}
+
+[GenericTypeAttribute<Vector3[]>()]
+public class ClassVariantAnnotatedVector3Array
+{
+}
+
+[GenericTypeAttribute<Color[]>()]
+public class ClassVariantAnnotatedColorArray
+{
+}
+
+[GenericTypeAttribute<GodotObject[]>()]
+public class ClassVariantAnnotatedGodotObjectArray
+{
+}
+
+[GenericTypeAttribute<StringName[]>()]
+public class ClassVariantAnnotatedStringNameArray
+{
+}
+
+[GenericTypeAttribute<NodePath[]>()]
+public class ClassVariantAnnotatedNodePathArray
+{
+}
+
+[GenericTypeAttribute<Rid[]>()]
+public class ClassVariantAnnotatedRidArray
+{
+}
+
+// This class definition fails because generic type is not Variant-compatible.
+[GenericTypeAttribute<{|GD0301:object|}>()]
+public class ClassNonVariantAnnotated
+{
+}
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs
new file mode 100644
index 0000000000..ce182e8c62
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs
@@ -0,0 +1,27 @@
+using Godot;
+
+public class MustBeVariantGD0302
+{
+ public void MethodOk<[MustBeVariant] T>()
+ {
+ // T is guaranteed to be a Variant-compatible type because it's annotated with the [MustBeVariant] attribute, so it can be used here.
+ new ExampleClass<T>();
+ Method<T>();
+ }
+
+ public void MethodFail<T>()
+ {
+ // These two calls raise a GD0302 diagnostic error: T is not valid here because it may not a Variant type and method call and class require it.
+ new ExampleClass<{|GD0302:T|}>();
+ Method<{|GD0302:T|}>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+}
+
+public class ExampleClass<[MustBeVariant] T>
+{
+
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs
new file mode 100644
index 0000000000..fa591bc873
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs
@@ -0,0 +1,7 @@
+namespace System.Diagnostics.CodeAnalysis
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
+ public sealed class NotNullAttribute : Attribute
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
index 147ef852b3..1113629fef 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -146,7 +146,7 @@ namespace Godot.SourceGenerators
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
- helpLinkUri: string.Format(_helpLinkFormat, "GD1003")),
+ helpLinkUri: string.Format(_helpLinkFormat, "GD0103")),
location,
location?.SourceTree?.FilePath));
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
index 7d2395ba61..b1578fb5f4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
- <LangVersion>9.0</LangVersion>
+ <LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs
new file mode 100644
index 0000000000..aecf127686
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.SourceGenerators
+{
+ public static class Helper
+ {
+ [Conditional("DEBUG")]
+ public static void ThrowIfNull([NotNull] object? value)
+ {
+ _ = value ?? throw new ArgumentNullException();
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
index 2a9758516c..b4f78fd218 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
@@ -1,5 +1,4 @@
using System.Collections.Immutable;
-using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -34,7 +33,7 @@ namespace Godot.SourceGenerators
// Method invocation or variable declaration that contained the type arguments
var parentSyntax = context.Node.Parent;
- Debug.Assert(parentSyntax != null);
+ Helper.ThrowIfNull(parentSyntax);
var sm = context.SemanticModel;
@@ -49,9 +48,10 @@ namespace Godot.SourceGenerators
continue;
var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol;
- Debug.Assert(typeSymbol != null);
+ Helper.ThrowIfNull(typeSymbol);
var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol;
+ Helper.ThrowIfNull(parentSymbol);
if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i))
{
@@ -69,7 +69,7 @@ namespace Godot.SourceGenerators
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(typeSymbol, typeCache);
- if (marshalType == null)
+ if (marshalType is null)
{
Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol);
continue;
@@ -109,11 +109,19 @@ namespace Godot.SourceGenerators
/// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns>
private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex)
{
- var typeParamSymbol = parentSymbol switch
+ ITypeParameterSymbol? typeParamSymbol = parentSymbol switch
{
- IMethodSymbol methodSymbol => methodSymbol.TypeParameters[typeArgumentIndex],
- INamedTypeSymbol typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex],
- _ => null,
+ IMethodSymbol methodSymbol when parentSyntax.Parent is AttributeSyntax &&
+ methodSymbol.ContainingType.TypeParameters.Length > 0
+ => methodSymbol.ContainingType.TypeParameters[typeArgumentIndex],
+
+ IMethodSymbol { TypeParameters.Length: > 0 } methodSymbol
+ => methodSymbol.TypeParameters[typeArgumentIndex],
+
+ INamedTypeSymbol { TypeParameters.Length: > 0 } typeSymbol
+ => typeSymbol.TypeParameters[typeArgumentIndex],
+ _
+ => null
};
if (typeParamSymbol == null)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
index 7232e4d7d7..2aa0519269 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
@@ -144,7 +144,7 @@ namespace Godot.SourceGenerators
.Append(" /// </summary>\n");
source.Append(
- $" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n");
+ $" public new class MethodName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.MethodName {{\n");
// Generate cached StringNames for methods and properties, for fast lookup
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
index 01aafe9c74..d6d8a93e03 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -54,9 +54,7 @@ namespace Godot.SourceGenerators
)
.Where(x =>
// Ignore classes whose name is not the same as the file name
- Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name &&
- // Ignore generic classes
- !x.symbol.IsGenericType)
+ Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name)
.GroupBy(x => x.symbol)
.ToDictionary(g => g.Key, g => g.Select(x => x.cds));
@@ -160,6 +158,8 @@ namespace Godot.SourceGenerators
first = false;
sourceBuilder.Append("typeof(");
sourceBuilder.Append(qualifiedName);
+ if (godotClass.Key.IsGenericType)
+ sourceBuilder.Append($"<{new string(',', godotClass.Key.TypeParameters.Count() - 1)}>");
sourceBuilder.Append(")");
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index de44ada6de..6e034c6e72 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -133,7 +133,7 @@ namespace Godot.SourceGenerators
.Append(" /// </summary>\n");
source.Append(
- $" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n");
+ $" public new class PropertyName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n");
// Generate cached StringNames for methods and properties, for fast lookup
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
index 5409d1a961..5246cc5780 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -185,7 +185,7 @@ namespace Godot.SourceGenerators
.Append(" /// </summary>\n");
source.Append(
- $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n");
+ $" public new class SignalName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.SignalName {{\n");
// Generate cached StringNames for methods and properties, for fast lookup
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
index 9e36497b06..d0972f1eae 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
- <TargetFramework>netstandard2.0</TargetFramework>
- <LangVersion>7.2</LangVersion>
+ <TargetFramework>net6.0</TargetFramework>
+ <LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" />
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
index d2132115f3..eb6ac8bafc 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
@@ -2,13 +2,10 @@
<PropertyGroup>
<ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid>
<OutputType>Exe</OutputType>
- <TargetFramework>net472</TargetFramework>
- <LangVersion>7.2</LangVersion>
+ <TargetFramework>net6.0</TargetFramework>
+ <LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
<ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
</ItemGroup>
<ItemGroup>
diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
index c05096bdcc..22778f21cb 100644
--- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
@@ -1,12 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup>
- <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid>
- <OutputType>Exe</OutputType>
- <TargetFramework>net472</TargetFramework>
- <LangVersion>7.2</LangVersion>
- </PropertyGroup>
- <ItemGroup>
- <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="EnvDTE" Version="8.0.2" />
- </ItemGroup>
+ <PropertyGroup>
+ <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net6.0-windows</TargetFramework>
+ <LangVersion>10</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition="Exists('$(SolutionDir)/../../../../bin/GodotSharp/Api/Debug/GodotSharp.dll') And ('$(GodotPlatform)' == 'windows' Or ('$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT'))">
+ <OutputPath>$(SolutionDir)/../../../../bin/GodotSharp/Tools</OutputPath>
+ <AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="EnvDTE" Version="17.8.37221" />
+ </ItemGroup>
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
index cc0deb6cc0..8e8eaa79b8 100644
--- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
@@ -129,7 +129,7 @@ namespace GodotTools.OpenVisualStudio
{
var mainWindow = dte.MainWindow;
mainWindow.Activate();
- SetForegroundWindow(new IntPtr(mainWindow.HWnd));
+ SetForegroundWindow(mainWindow.HWnd);
MessageFilter.Revoke();
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs
deleted file mode 100644
index 345a472185..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace GodotTools
-{
- public static class ApiAssemblyNames
- {
- public const string SolutionName = "GodotSharp";
- public const string Core = "GodotSharp";
- public const string Editor = "GodotSharpEditor";
- }
-
- public enum ApiAssemblyType
- {
- Core,
- Editor
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 7cf98b8f1f..6e99483a1c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -24,6 +24,20 @@ namespace GodotTools.Build
public static event Action<string> StdOutputReceived;
public static event Action<string> StdErrorReceived;
+ public static DateTime LastValidBuildDateTime { get; private set; }
+
+ static BuildManager()
+ {
+ UpdateLastValidBuildDateTime();
+ }
+
+ public static void UpdateLastValidBuildDateTime()
+ {
+ var dllName = $"{GodotSharpDirs.ProjectAssemblyName}.dll";
+ var path = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "Debug", dllName);
+ LastValidBuildDateTime = File.GetLastWriteTime(path);
+ }
+
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
{
string issuesFile = GetIssuesFilePath(buildInfo);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
index b23b3f42ef..d62eb3ce9e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
@@ -308,14 +308,14 @@ namespace GodotTools.Build
return false;
string searchText = _searchBox.Text;
- if (!string.IsNullOrEmpty(searchText) &&
- (!diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
- !(diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false)))
- {
- return false;
- }
-
- return true;
+ if (string.IsNullOrEmpty(searchText))
+ return true;
+ if (diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+ if (diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false)
+ return true;
+
+ return false;
}
private Color? GetProblemItemColor(BuildDiagnostic diagnostic)
@@ -516,7 +516,7 @@ namespace GodotTools.Build
public override void _Ready()
{
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
_layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>();
Name = "Problems".TTR();
@@ -655,7 +655,7 @@ namespace GodotTools.Build
switch ((long)what)
{
case EditorSettings.NotificationEditorSettingsChanged:
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
_layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>();
_toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState();
UpdateProblemsView();
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index bae87dd1dd..9a02c9ca88 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -89,7 +89,10 @@ namespace GodotTools.Build
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
private void RebuildProject()
@@ -107,7 +110,10 @@ namespace GodotTools.Build
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
private void CleanProject()
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
deleted file mode 100644
index e516b4dd18..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ /dev/null
@@ -1,623 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Text;
-using GodotTools.Internals;
-using Directory = GodotTools.Utils.Directory;
-using File = GodotTools.Utils.File;
-using OS = GodotTools.Utils.OS;
-using Path = System.IO.Path;
-
-namespace GodotTools.Export
-{
- public struct AotOptions
- {
- public bool EnableLLVM;
- public bool LLVMOnly;
- public string LLVMPath;
- public string LLVMOutputPath;
-
- public bool FullAot;
-
- private bool _useInterpreter;
- public bool UseInterpreter { readonly get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; }
-
- public string[] ExtraAotOptions;
- public string[] ExtraOptimizerOptions;
-
- public string ToolchainPath;
- }
-
- public static class AotBuilder
- {
- public static void CompileAssemblies(ExportPlugin exporter, AotOptions aotOpts, string[] features, string platform, bool isDebug, string bclDir, string outputDir, string outputDataDir, IDictionary<string, string> assemblies)
- {
- // TODO: WASM
-
- string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
-
- if (!Directory.Exists(aotTempDir))
- Directory.CreateDirectory(aotTempDir);
-
- var assembliesPrepared = new Dictionary<string, string>();
-
- foreach (var dependency in assemblies)
- {
- string assemblyName = dependency.Key;
- string assemblyPath = dependency.Value;
-
- string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
-
- if (File.Exists(assemblyPathInBcl))
- {
- // Don't create teporaries for assemblies from the BCL
- assembliesPrepared.Add(assemblyName, assemblyPathInBcl);
- }
- else
- {
- string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
- File.Copy(assemblyPath, tempAssemblyPath);
- assembliesPrepared.Add(assemblyName, tempAssemblyPath);
- }
- }
-
- if (platform == OS.Platforms.iOS)
- {
- string[] architectures = GetEnablediOSArchs(features).ToArray();
- CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir);
- }
- else if (platform == OS.Platforms.Android)
- {
- string[] abis = GetEnabledAndroidAbis(features).ToArray();
- CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir);
- }
- else
- {
- string arch = "";
- if (features.Contains("x86_64"))
- {
- arch = "x86_64";
- }
- else if (features.Contains("x86_32"))
- {
- arch = "x86_32";
- }
- else if (features.Contains("arm64"))
- {
- arch = "arm64";
- }
- else if (features.Contains("arm32"))
- {
- arch = "arm32";
- }
- CompileAssembliesForDesktop(exporter, platform, isDebug, arch, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir);
- }
- }
-
- public static void CompileAssembliesForAndroid(ExportPlugin exporter, bool isDebug, string[] abis, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
- {
-
- foreach (var assembly in assemblies)
- {
- string assemblyName = assembly.Key;
- string assemblyPath = assembly.Value;
-
- // Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
- // but we use '-aot-' as well just in case to avoid conflicts with other libs.
- string outputFileName = "lib-aot-" + assemblyName + ".dll.so";
-
- foreach (string abi in abis)
- {
- string aotAbiTempDir = Path.Combine(aotTempDir, abi);
- string soFilePath = Path.Combine(aotAbiTempDir, outputFileName);
-
- var compilerArgs = GetAotCompilerArgs(OS.Platforms.Android, isDebug, abi, aotOpts, assemblyPath, soFilePath);
-
- // Make sure the output directory exists
- Directory.CreateDirectory(aotAbiTempDir);
-
- string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.Android}-{abi}");
-
- ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
-
- // The Godot exporter expects us to pass the abi in the tags parameter
- exporter.AddSharedObject(soFilePath, tags: new[] { abi }, "");
- }
- }
- }
-
- public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string arch, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir)
- {
- foreach (var assembly in assemblies)
- {
- string assemblyName = assembly.Key;
- string assemblyPath = assembly.Value;
-
- string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" :
- platform == OS.Platforms.MacOS ? ".dylib" :
- ".so";
-
- string outputFileName = assemblyName + ".dll" + outputFileExtension;
- string tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
-
- var compilerArgs = GetAotCompilerArgs(platform, isDebug, arch, aotOpts, assemblyPath, tempOutputFilePath);
-
- string compilerDirPath = GetMonoCrossDesktopDirName(platform, arch);
-
- ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
-
- if (platform == OS.Platforms.MacOS)
- {
- exporter.AddSharedObject(tempOutputFilePath, tags: null, "");
- }
- else
- {
- string libDir = platform == OS.Platforms.Windows ? "bin" : "lib";
- string outputDataLibDir = Path.Combine(outputDataDir, "Mono", libDir);
- File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName));
- }
- }
- }
-
- public static void CompileAssembliesForiOS(ExportPlugin exporter, bool isDebug, string[] architectures, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
- {
- var cppCode = new StringBuilder();
- var aotModuleInfoSymbols = new List<string>(assemblies.Count);
-
- // {arch: paths}
- var objFilePathsForiOSArch = architectures.ToDictionary(arch => arch, arch => new List<string>(assemblies.Count));
-
- foreach (var assembly in assemblies)
- {
- string assemblyName = assembly.Key;
- string assemblyPath = assembly.Value;
-
- string asmFileName = assemblyName + ".dll.S";
- string objFileName = assemblyName + ".dll.o";
-
- foreach (string arch in architectures)
- {
- string aotArchTempDir = Path.Combine(aotTempDir, arch);
- string asmFilePath = Path.Combine(aotArchTempDir, asmFileName);
-
- var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug, arch, aotOpts, assemblyPath, asmFilePath);
-
- // Make sure the output directory exists
- Directory.CreateDirectory(aotArchTempDir);
-
- string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.iOS}-{arch}");
-
- ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
-
- // Assembling
- bool isSim = arch == "i386" || arch == "x86_64"; // Shouldn't really happen as we don't do AOT for the simulator
- string versionMinName = isSim ? "iphonesimulator" : "iphoneos";
- string iOSPlatformName = isSim ? "iPhoneSimulator" : "iPhoneOS";
- const string versionMin = "12.0"; // TODO: Turn this hard-coded version into an exporter setting
- string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath,
- $"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk");
-
- string objFilePath = Path.Combine(aotArchTempDir, objFileName);
-
- var clangArgs = new List<string>()
- {
- "-isysroot", iOSSdkPath,
- "-Qunused-arguments",
- $"-m{versionMinName}-version-min={versionMin}",
- "-arch", arch,
- "-c",
- "-o", objFilePath,
- "-x", "assembler"
- };
-
- if (isDebug)
- clangArgs.Add("-DDEBUG");
-
- clangArgs.Add(asmFilePath);
-
- int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs);
- if (clangExitCode != 0)
- throw new InvalidOperationException($"Command 'clang' exited with code: {clangExitCode}.");
-
- objFilePathsForiOSArch[arch].Add(objFilePath);
- }
-
- aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info");
- }
-
- // Generate driver code
- cppCode.AppendLine("#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)");
- cppCode.AppendLine("#define IOS_DEVICE");
- cppCode.AppendLine("#endif");
-
- cppCode.AppendLine("#ifdef IOS_DEVICE");
- cppCode.AppendLine("extern \"C\" {");
- cppCode.AppendLine("// Mono API");
- cppCode.AppendLine(@"
-typedef enum {
-MONO_AOT_MODE_NONE,
-MONO_AOT_MODE_NORMAL,
-MONO_AOT_MODE_HYBRID,
-MONO_AOT_MODE_FULL,
-MONO_AOT_MODE_LLVMONLY,
-MONO_AOT_MODE_INTERP,
-MONO_AOT_MODE_INTERP_LLVMONLY,
-MONO_AOT_MODE_LLVMONLY_INTERP,
-MONO_AOT_MODE_LAST = 1000,
-} MonoAotMode;");
- cppCode.AppendLine("void mono_jit_set_aot_mode(MonoAotMode);");
- cppCode.AppendLine("void mono_aot_register_module(void *);");
-
- if (aotOpts.UseInterpreter)
- {
- cppCode.AppendLine("void mono_ee_interp_init(const char *);");
- cppCode.AppendLine("void mono_icall_table_init();");
- cppCode.AppendLine("void mono_marshal_ilgen_init();");
- cppCode.AppendLine("void mono_method_builder_ilgen_init();");
- cppCode.AppendLine("void mono_sgen_mono_ilgen_init();");
- }
-
- foreach (string symbol in aotModuleInfoSymbols)
- cppCode.AppendLine($"extern void *{symbol};");
-
- cppCode.AppendLine("void gd_mono_setup_aot() {");
-
- foreach (string symbol in aotModuleInfoSymbols)
- cppCode.AppendLine($"\tmono_aot_register_module({symbol});");
-
- if (aotOpts.UseInterpreter)
- {
- cppCode.AppendLine("\tmono_icall_table_init();");
- cppCode.AppendLine("\tmono_marshal_ilgen_init();");
- cppCode.AppendLine("\tmono_method_builder_ilgen_init();");
- cppCode.AppendLine("\tmono_sgen_mono_ilgen_init();");
- cppCode.AppendLine("\tmono_ee_interp_init(0);");
- }
-
- string aotModeStr = null;
-
- if (aotOpts.LLVMOnly)
- {
- aotModeStr = "MONO_AOT_MODE_LLVMONLY"; // --aot=llvmonly
- }
- else
- {
- if (aotOpts.UseInterpreter)
- aotModeStr = "MONO_AOT_MODE_INTERP"; // --aot=interp or --aot=interp,full
- else if (aotOpts.FullAot)
- aotModeStr = "MONO_AOT_MODE_FULL"; // --aot=full
- }
-
- // One of the options above is always set for iOS
- Debug.Assert(aotModeStr != null);
-
- cppCode.AppendLine($"\tmono_jit_set_aot_mode({aotModeStr});");
-
- cppCode.AppendLine("} // gd_mono_setup_aot");
- cppCode.AppendLine("} // extern \"C\"");
- cppCode.AppendLine("#endif // IOS_DEVICE");
-
- // Add the driver code to the Xcode project
- exporter.AddIosCppCode(cppCode.ToString());
-
- // Archive the AOT object files into a static library
-
- var arFilePathsForAllArchs = new List<string>();
- string projectAssemblyName = GodotSharpDirs.ProjectAssemblyName;
-
- foreach (var archPathsPair in objFilePathsForiOSArch)
- {
- string arch = archPathsPair.Key;
- var objFilePaths = archPathsPair.Value;
-
- string arOutputFilePath = Path.Combine(aotTempDir, $"lib-aot-{projectAssemblyName}.{arch}.a");
-
- var arArgs = new List<string>()
- {
- "cr",
- arOutputFilePath
- };
-
- foreach (string objFilePath in objFilePaths)
- arArgs.Add(objFilePath);
-
- int arExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("ar"), arArgs);
- if (arExitCode != 0)
- throw new InvalidOperationException($"Command 'ar' exited with code: {arExitCode}.");
-
- arFilePathsForAllArchs.Add(arOutputFilePath);
- }
-
- // It's lipo time
-
- string fatOutputFileName = $"lib-aot-{projectAssemblyName}.fat.a";
- string fatOutputFilePath = Path.Combine(aotTempDir, fatOutputFileName);
-
- var lipoArgs = new List<string>();
- lipoArgs.Add("-create");
- lipoArgs.AddRange(arFilePathsForAllArchs);
- lipoArgs.Add("-output");
- lipoArgs.Add(fatOutputFilePath);
-
- int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
- if (lipoExitCode != 0)
- throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
-
- // TODO: Add the AOT lib and interpreter libs as device only to suppress warnings when targeting the simulator
-
- // Add the fat AOT static library to the Xcode project
- exporter.AddIosProjectStaticLib(fatOutputFilePath);
-
- // Add the required Mono libraries to the Xcode project
-
- string MonoLibFile(string libFileName) => libFileName + ".ios.fat.a";
-
- string MonoLibFromTemplate(string libFileName) =>
- Path.Combine(Internal.FullExportTemplatesDir, "ios-mono-libs", MonoLibFile(libFileName));
-
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmonosgen-2.0"));
-
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-native"));
-
- if (aotOpts.UseInterpreter)
- {
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ee-interp"));
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-icall-table"));
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ilgen"));
- }
-
- // TODO: Turn into an exporter option
- bool enableProfiling = false;
- if (enableProfiling)
- exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-profiler-log"));
-
- // Add frameworks required by Mono to the Xcode project
- exporter.AddIosFramework("libiconv.tbd");
- exporter.AddIosFramework("GSS.framework");
- exporter.AddIosFramework("CFNetwork.framework");
-
- // Force load and export dynamic are needed for the linker to not strip required symbols.
- // In theory we shouldn't be relying on this for P/Invoked functions (as is the case with
- // functions in System.Native/libmono-native). Instead, we should use cecil to search for
- // DllImports in assemblies and pass them to 'ld' as '-u/--undefined {pinvoke_symbol}'.
- exporter.AddIosLinkerFlags("-rdynamic");
- exporter.AddIosLinkerFlags($"-force_load \"$(SRCROOT)/{MonoLibFile("libmono-native")}\"");
- }
-
- /// Converts an assembly name to a valid symbol name in the same way the AOT compiler does
- private static string AssemblyNameToAotSymbol(string assemblyName)
- {
- var builder = new StringBuilder();
-
- foreach (var charByte in Encoding.UTF8.GetBytes(assemblyName))
- {
- char @char = (char)charByte;
- builder.Append(Char.IsLetterOrDigit(@char) || @char == '_' ? @char : '_');
- }
-
- return builder.ToString();
- }
-
- private static IEnumerable<string> GetAotCompilerArgs(string platform, bool isDebug, string target, AotOptions aotOpts, string assemblyPath, string outputFilePath)
- {
- // TODO: LLVM
-
- bool aotSoftDebug = isDebug && !aotOpts.EnableLLVM;
- bool aotDwarfDebug = platform == OS.Platforms.iOS;
-
- var aotOptions = new List<string>();
- var optimizerOptions = new List<string>();
-
- if (aotOpts.LLVMOnly)
- {
- aotOptions.Add("llvmonly");
- }
- else
- {
- // Can be both 'interp' and 'full'
- if (aotOpts.UseInterpreter)
- aotOptions.Add("interp");
- if (aotOpts.FullAot)
- aotOptions.Add("full");
- }
-
- aotOptions.Add(aotSoftDebug ? "soft-debug" : "nodebug");
-
- if (aotDwarfDebug)
- aotOptions.Add("dwarfdebug");
-
- if (platform == OS.Platforms.Android)
- {
- string abi = target;
-
- string androidToolchain = aotOpts.ToolchainPath;
-
- if (string.IsNullOrEmpty(androidToolchain))
- {
- androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
-
- if (!Directory.Exists(androidToolchain))
- throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
- }
- else if (!Directory.Exists(androidToolchain))
- {
- throw new FileNotFoundException($"Android toolchain not found: '{androidToolchain}'.");
- }
-
- var androidToolPrefixes = new Dictionary<string, string>
- {
- ["arm32"] = "arm-linux-androideabi-",
- ["arm64"] = "aarch64-linux-android-",
- ["x86_32"] = "i686-linux-android-",
- ["x86_64"] = "x86_64-linux-android-"
- };
-
- aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
-
- string triple = GetAndroidTriple(abi);
- aotOptions.Add($"mtriple={triple}");
- }
- else if (platform == OS.Platforms.iOS)
- {
- if (!aotOpts.LLVMOnly && !aotOpts.UseInterpreter)
- optimizerOptions.Add("gsharedvt");
-
- aotOptions.Add("static");
-
- // I couldn't get the Mono cross-compiler to do assembling, so we'll have to do it ourselves
- aotOptions.Add("asmonly");
-
- aotOptions.Add("direct-icalls");
-
- if (aotSoftDebug)
- aotOptions.Add("no-direct-calls");
-
- if (aotOpts.LLVMOnly || !aotOpts.UseInterpreter)
- aotOptions.Add("direct-pinvoke");
-
- string arch = target;
- aotOptions.Add($"mtriple={arch}-ios");
- }
-
- aotOptions.Add($"outfile={outputFilePath}");
-
- if (aotOpts.EnableLLVM)
- {
- aotOptions.Add($"llvm-path={aotOpts.LLVMPath}");
- aotOptions.Add($"llvm-outfile={aotOpts.LLVMOutputPath}");
- }
-
- if (aotOpts.ExtraAotOptions.Length > 0)
- aotOptions.AddRange(aotOpts.ExtraAotOptions);
-
- if (aotOpts.ExtraOptimizerOptions.Length > 0)
- optimizerOptions.AddRange(aotOpts.ExtraOptimizerOptions);
-
- string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
- string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
-
- var runtimeArgs = new List<string>();
-
- // The '--debug' runtime option is required when using the 'soft-debug' and 'dwarfdebug' AOT options
- if (aotSoftDebug || aotDwarfDebug)
- runtimeArgs.Add("--debug");
-
- if (aotOpts.EnableLLVM)
- runtimeArgs.Add("--llvm");
-
- runtimeArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
-
- if (optimizerOptions.Count > 0)
- runtimeArgs.Add($"-O={OptionsToString(optimizerOptions)}");
-
- runtimeArgs.Add(assemblyPath);
-
- return runtimeArgs;
- }
-
- private static void ExecuteCompiler(string compiler, IEnumerable<string> compilerArgs, string bclDir)
- {
- // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
- string CmdLineArgsToString(IEnumerable<string> args)
- {
- // Not perfect, but as long as we are careful...
- return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
- }
-
- using (var process = new Process())
- {
- process.StartInfo = new ProcessStartInfo(compiler, CmdLineArgsToString(compilerArgs))
- {
- UseShellExecute = false
- };
-
- process.StartInfo.EnvironmentVariables.Remove("MONO_ENV_OPTIONS");
- process.StartInfo.EnvironmentVariables.Remove("MONO_THREADS_SUSPEND");
- process.StartInfo.EnvironmentVariables.Add("MONO_PATH", bclDir);
-
- Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
-
- if (!process.Start())
- throw new InvalidOperationException("Failed to start process for Mono AOT compiler.");
-
- process.WaitForExit();
-
- if (process.ExitCode != 0)
- throw new InvalidOperationException($"Mono AOT compiler exited with code: {process.ExitCode}.");
- }
- }
-
- private static IEnumerable<string> GetEnablediOSArchs(string[] features)
- {
- var iosArchs = new[]
- {
- "arm64"
- };
-
- return iosArchs.Where(features.Contains);
- }
-
- private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
- {
- var androidAbis = new[]
- {
- "arm32",
- "arm64",
- "x86_32",
- "x86_64"
- };
-
- return androidAbis.Where(features.Contains);
- }
-
- private static string GetAndroidTriple(string abi)
- {
- var abiArchs = new Dictionary<string, string>
- {
- ["arm32"] = "armv7",
- ["arm64"] = "aarch64-v8a",
- ["x86_32"] = "i686",
- ["x86_64"] = "x86_64"
- };
-
- string arch = abiArchs[abi];
-
- return $"{arch}-linux-android";
- }
-
- private static string GetMonoCrossDesktopDirName(string platform, string arch)
- {
- switch (platform)
- {
- case OS.Platforms.Windows:
- {
- return $"windows-{arch}";
- }
- case OS.Platforms.MacOS:
- {
- return $"{platform}-{arch}";
- }
- case OS.Platforms.LinuxBSD:
- {
- return $"linux-{arch}";
- }
- default:
- throw new NotSupportedException($"Platform not supported: {platform}");
- }
- }
-
- // TODO: Replace this for a specific path for each platform
- private static string FindCrossCompiler(string monoCrossBin)
- {
- string exeExt = OS.IsWindows ? ".exe" : string.Empty;
-
- var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}", SearchOption.TopDirectoryOnly);
- if (files.Length > 0)
- return Path.Combine(monoCrossBin, files[0].Name);
-
- throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 650afb4cdf..44422eb862 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -9,6 +9,7 @@ using System.Linq;
using GodotTools.Build;
using GodotTools.Ides;
using GodotTools.Ides.Rider;
+using GodotTools.Inspector;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using JetBrains.Annotations;
@@ -45,6 +46,7 @@ namespace GodotTools
// TODO Use WeakReference once we have proper serialization.
private WeakRef _exportPluginWeak;
+ private WeakRef _inspectorPluginWeak;
public GodotIdeManager GodotIdeManager { get; private set; }
@@ -615,6 +617,11 @@ namespace GodotTools
AddExportPlugin(exportPlugin);
_exportPluginWeak = WeakRef(exportPlugin);
+ // Inspector plugin
+ var inspectorPlugin = new InspectorPlugin();
+ AddInspectorPlugin(inspectorPlugin);
+ _inspectorPluginWeak = WeakRef(inspectorPlugin);
+
BuildManager.Initialize();
RiderPathManager.Initialize();
@@ -627,6 +634,9 @@ namespace GodotTools
base._DisablePlugin();
_editorSettings.SettingsChanged -= OnSettingsChanged;
+
+ // Custom signals aren't automatically disconnected currently.
+ MSBuildPanel.BuildStateChanged -= BuildStateChanged;
}
public override void _ExitTree()
@@ -661,6 +671,13 @@ namespace GodotTools
_exportPluginWeak.Dispose();
}
+ if (IsInstanceValid(_inspectorPluginWeak))
+ {
+ (_inspectorPluginWeak.GetRef().AsGodotObject() as InspectorPlugin)?.Dispose();
+
+ _inspectorPluginWeak.Dispose();
+ }
+
GodotIdeManager?.Dispose();
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 56ae37b4dd..b5567ea3c7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -28,7 +28,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
- <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.4" />
+ <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.8" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<Reference Include="GodotSharp">
@@ -49,7 +49,5 @@
<ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" />
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
- <!-- Include it if this is an SCons build targeting Windows, or if it's not an SCons build but we're on Windows -->
- <ProjectReference Include="..\GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) " />
</ItemGroup>
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
index eb42f01b3a..66717d8636 100644
--- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -1,7 +1,7 @@
using Godot;
+using GodotTools.Build;
using GodotTools.Internals;
using JetBrains.Annotations;
-using static GodotTools.Internals.Globals;
namespace GodotTools
{
@@ -16,14 +16,20 @@ namespace GodotTools
RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
}
private void TimerTimeout()
{
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
[UsedImplicitly]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorOutOfSyncWarning.cs b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorOutOfSyncWarning.cs
new file mode 100644
index 0000000000..c5c451f143
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorOutOfSyncWarning.cs
@@ -0,0 +1,37 @@
+using Godot;
+using GodotTools.Internals;
+
+namespace GodotTools.Inspector
+{
+ public partial class InspectorOutOfSyncWarning : HBoxContainer
+ {
+ public override void _Ready()
+ {
+ SetAnchorsPreset(LayoutPreset.TopWide);
+
+ var iconTexture = GetThemeIcon("StatusWarning", "EditorIcons");
+
+ var icon = new TextureRect()
+ {
+ Texture = iconTexture,
+ ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional,
+ CustomMinimumSize = iconTexture.GetSize(),
+ };
+
+ icon.SizeFlagsVertical = SizeFlags.ShrinkCenter;
+
+ var label = new Label()
+ {
+ Text = "This inspector might be out of date. Please build the C# project.".TTR(),
+ AutowrapMode = TextServer.AutowrapMode.WordSmart,
+ CustomMinimumSize = new Vector2(100f, 0f),
+ };
+
+ label.AddThemeColorOverride("font_color", GetThemeColor("warning_color", "Editor"));
+ label.SizeFlagsHorizontal = SizeFlags.Fill | SizeFlags.Expand;
+
+ AddChild(icon);
+ AddChild(label);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
new file mode 100644
index 0000000000..b86a2b8b24
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+using GodotTools.Build;
+using GodotTools.Utils;
+
+namespace GodotTools.Inspector
+{
+ public partial class InspectorPlugin : EditorInspectorPlugin
+ {
+ public override bool _CanHandle(GodotObject godotObject)
+ {
+ foreach (var script in EnumerateScripts(godotObject))
+ {
+ if (script is CSharpScript)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public override void _ParseBegin(GodotObject godotObject)
+ {
+ foreach (var script in EnumerateScripts(godotObject))
+ {
+ if (script is not CSharpScript)
+ continue;
+
+ string scriptPath = script.ResourcePath;
+ if (scriptPath.StartsWith("csharp://"))
+ {
+ // This is a virtual path used by generic types, extract the real path.
+ var scriptPathSpan = scriptPath.AsSpan("csharp://".Length);
+ scriptPathSpan = scriptPathSpan[..scriptPathSpan.IndexOf(':')];
+ scriptPath = $"res://{scriptPathSpan}";
+ }
+
+ if (File.GetLastWriteTime(scriptPath) > BuildManager.LastValidBuildDateTime)
+ {
+ AddCustomControl(new InspectorOutOfSyncWarning());
+ break;
+ }
+ }
+ }
+
+ private static IEnumerable<Script> EnumerateScripts(GodotObject godotObject)
+ {
+ var script = godotObject.GetScript().As<Script>();
+ while (script != null)
+ {
+ yield return script;
+ script = script.GetBaseScript();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
deleted file mode 100644
index 9a8fdcc7c5..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace GodotTools
-{
- public readonly struct PlaySettings
- {
- public bool HasDebugger { get; }
- public string DebuggerHost { get; }
- public int DebuggerPort { get; }
-
- public bool BuildBeforePlaying { get; }
-
- public PlaySettings(string debuggerHost, int debuggerPort, bool buildBeforePlaying)
- {
- HasDebugger = true;
- DebuggerHost = debuggerHost;
- DebuggerPort = debuggerPort;
- BuildBeforePlaying = buildBeforePlaying;
- }
- }
-}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 25a5720bc4..675651ccf7 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1621,9 +1621,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
} else {
// Hide the constructor
- output.append(MEMBER_BEGIN "internal ");
- output.append(itype.proxy_name);
- output.append("() {}\n");
+ output << MEMBER_BEGIN "internal " << itype.proxy_name << "() : this("
+ << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
+ << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
+ << INDENT3 "_ConstructAndInitialize(null, "
+ << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
+ << (itype.is_ref_counted ? "true" : "false") << ");\n"
+ << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
}
// Add.. em.. trick constructor. Sort of.
@@ -2237,10 +2241,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
p_output.append(INDENT1 "/// </summary>");
}
-
- if (p_imethod.method_doc->is_deprecated) {
- p_output.append(MEMBER_BEGIN "[Obsolete(\"This method is deprecated.\")]");
- }
}
if (default_args_doc.get_string_length()) {
@@ -2255,6 +2255,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
p_output.append(p_imethod.deprecation_message);
p_output.append("\")]");
+ } else if (p_imethod.method_doc && p_imethod.method_doc->is_deprecated) {
+ p_output.append(MEMBER_BEGIN "[Obsolete(\"This method is deprecated.\")]");
}
if (p_imethod.is_compat) {
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index fc99f3ceda..05dacd28fb 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -43,10 +43,10 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
+#include "editor/themes/editor_scale.h"
#include "main/main.h"
#ifdef UNIX_ENABLED
@@ -148,7 +148,7 @@ void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) {
}
void godot_icall_Internal_EditorDebuggerNodeReloadScripts() {
- EditorDebuggerNode::get_singleton()->reload_scripts();
+ EditorDebuggerNode::get_singleton()->reload_all_scripts();
}
bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) {
@@ -175,7 +175,7 @@ void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_contr
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
if (ed) {
- ed->reload_scripts();
+ ed->reload_all_scripts();
}
}
diff --git a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs
index 87468fb433..698157c6b4 100644
--- a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs
+++ b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs
@@ -8,20 +8,21 @@ public partial class _CLASS_ : _BASE_
public const float Speed = 300.0f;
public const float JumpVelocity = -400.0f;
- // Get the gravity from the project settings to be synced with RigidBody nodes.
- public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
-
public override void _PhysicsProcess(double delta)
{
Vector2 velocity = Velocity;
// Add the gravity.
if (!IsOnFloor())
- velocity.Y += gravity * (float)delta;
+ {
+ velocity += GetGravity() * (float)delta;
+ }
// Handle Jump.
if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
+ {
velocity.Y = JumpVelocity;
+ }
// Get the input direction and handle the movement/deceleration.
// As good practice, you should replace UI actions with custom gameplay actions.
diff --git a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs
index ddeb9d7e00..30dabd31d9 100644
--- a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs
+++ b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs
@@ -8,20 +8,21 @@ public partial class _CLASS_ : _BASE_
public const float Speed = 5.0f;
public const float JumpVelocity = 4.5f;
- // Get the gravity from the project settings to be synced with RigidBody nodes.
- public float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle();
-
public override void _PhysicsProcess(double delta)
{
Vector3 velocity = Velocity;
// Add the gravity.
if (!IsOnFloor())
- velocity.Y -= gravity * (float)delta;
+ {
+ velocity += GetGravity() * (float)delta;
+ }
// Handle Jump.
if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
+ {
velocity.Y = JumpVelocity;
+ }
// Get the input direction and handle the movement/deceleration.
// As good practice, you should replace UI actions with custom gameplay actions.
diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig
index df4a6c2d0d..987e6c543b 100644
--- a/modules/mono/glue/GodotSharp/.editorconfig
+++ b/modules/mono/glue/GodotSharp/.editorconfig
@@ -6,6 +6,9 @@ dotnet_diagnostic.CA1062.severity = error
dotnet_diagnostic.CA1069.severity = none
# CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1708.severity = none
+# CA1716: Identifiers should not match keywords
+# This is suppressed, because it will report `@event` as well as `event`
+dotnet_diagnostic.CA1716.severity = none
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# CS1573: Parameter has no matching param tag in the XML comment
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index 57b5b09ebb..63af6ee6e8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -98,11 +98,11 @@ namespace Godot
Vector3 dstMax = with._position + with._size;
return srcMin.X <= dstMin.X &&
- srcMax.X > dstMax.X &&
+ srcMax.X >= dstMax.X &&
srcMin.Y <= dstMin.Y &&
- srcMax.Y > dstMax.Y &&
+ srcMax.Y >= dstMax.Y &&
srcMin.Z <= dstMin.Z &&
- srcMax.Z > dstMax.Z;
+ srcMax.Z >= dstMax.Z;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 6024073f8a..fa74d5e101 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -16,7 +16,9 @@ namespace Godot.Collections
/// interfacing with the engine. Otherwise prefer .NET collections
/// such as <see cref="System.Array"/> or <see cref="List{T}"/>.
/// </summary>
+#pragma warning disable CA1710 // Identifiers should have correct suffix
public sealed class Array :
+#pragma warning restore CA1710
IList<Variant>,
IReadOnlyList<Variant>,
ICollection,
@@ -1142,7 +1144,7 @@ namespace Godot.Collections
/// </summary>
/// <param name="from">The typed array to convert.</param>
/// <returns>A new Godot Array, or <see langword="null"/> if <see paramref="from"/> was null.</returns>
- [return: NotNullIfNotNull(nameof(from))]
+ [return: NotNullIfNotNull("from")]
public static explicit operator Array?(Array<T>? from)
{
return from?._underlyingArray;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index 16be494d99..7b6b35b68f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
@@ -94,7 +94,7 @@ namespace Godot.Bridge
// Signals
if (godotObject.HasGodotClassSignal(CustomUnsafe.AsRef(name)))
{
- godot_signal signal = new godot_signal(*name, godotObject.GetInstanceId());
+ godot_signal signal = new godot_signal(NativeFuncs.godotsharp_string_name_new_copy(*name), godotObject.GetInstanceId());
*outRet = VariantUtils.CreateFromSignalTakingOwnershipOfDisposableValue(signal);
return godot_bool.True;
}
@@ -102,7 +102,7 @@ namespace Godot.Bridge
// Methods
if (godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(name)))
{
- godot_callable method = new godot_callable(*name, godotObject.GetInstanceId());
+ godot_callable method = new godot_callable(NativeFuncs.godotsharp_string_name_new_copy(*name), godotObject.GetInstanceId());
*outRet = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(method);
return godot_bool.True;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index a78cb0bba9..6c34d7c29d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -18,6 +18,7 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName;
+ public delegate* unmanaged<godot_string*, godot_string*, godot_string*, godot_string*, void> ScriptManagerBridge_GetGlobalClassName;
public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
@@ -25,7 +26,7 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
- public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+ public delegate* unmanaged<IntPtr, godot_csharp_type_info*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
@@ -60,6 +61,7 @@ namespace Godot.Bridge
ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName,
+ ScriptManagerBridge_GetGlobalClassName = &ScriptManagerBridge.GetGlobalClassName,
ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr,
ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal,
ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index a087b20308..0f271d6547 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Serialization;
+using System.Text;
using Godot.NativeInterop;
namespace Godot.Bridge
@@ -29,7 +30,7 @@ namespace Godot.Bridge
foreach (var type in typesInAlc.Keys)
{
if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
- !_pathTypeBiMap.TryGetScriptPath(type, out _))
+ (!_pathTypeBiMap.TryGetScriptPath(type, out string? scriptPath) || scriptPath.StartsWith("csharp://")))
{
// For scripts without a path, we need to keep the class qualified name for reloading
_scriptDataForReload.TryAdd(scriptPtr,
@@ -221,6 +222,71 @@ namespace Godot.Bridge
}
[UnmanagedCallersOnly]
+ internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_string* outBaseType, godot_string* outIconPath, godot_string* outClassName)
+ {
+ // This method must always return the outBaseType for every script, even if the script is
+ // not a global class. But if the script is not a global class it must return an empty
+ // outClassName string since it should not have a name.
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
+ Debug.Assert(!string.IsNullOrEmpty(scriptPathStr), "Script path can't be empty.");
+
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
+ {
+ // Script at the given path does not exist, or it's not a C# type.
+ // This is fine, it may be a path to a generic script and those can't be global classes.
+ *outClassName = default;
+ return;
+ }
+
+ if (outIconPath != null)
+ {
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ .OfType<IconAttribute>()
+ .FirstOrDefault();
+
+ *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ }
+
+ if (outBaseType != null)
+ {
+ bool foundGlobalBaseScript = false;
+
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
+ Type? top = scriptType.BaseType;
+
+ while (top != null && top != native)
+ {
+ if (IsGlobalClass(top))
+ {
+ *outBaseType = Marshaling.ConvertStringToNative(top.Name);
+ foundGlobalBaseScript = true;
+ break;
+ }
+
+ top = top.BaseType;
+ }
+ if (!foundGlobalBaseScript)
+ {
+ *outBaseType = Marshaling.ConvertStringToNative(native.Name);
+ }
+ }
+
+ if (!IsGlobalClass(scriptType))
+ {
+ // Scripts that are not global classes should not have a name.
+ // Return an empty string to prevent the class from being registered
+ // as a global class in the editor.
+ *outClassName = default;
+ return;
+ }
+
+ *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
+
+ static bool IsGlobalClass(Type scriptType) =>
+ scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
+ }
+
+ [UnmanagedCallersOnly]
internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr)
{
try
@@ -310,13 +376,6 @@ namespace Godot.Bridge
_pathTypeBiMap.Add(scriptPathAttr.Path, type);
- // This method may be called before initialization.
- if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint())
- {
- using godot_string scriptPath = Marshaling.ConvertStringToNative(scriptPathAttr.Path);
- NativeFuncs.godotsharp_internal_editor_file_system_update_file(scriptPath);
- }
-
if (AlcReloadCfg.IsAlcReloadingEnabled)
{
AddTypeForAlcReloading(type);
@@ -340,7 +399,7 @@ namespace Godot.Bridge
foreach (var type in assembly.GetTypes())
{
- if (type.IsNested || type.IsGenericType)
+ if (type.IsNested)
continue;
if (!typeOfGodotObject.IsAssignableFrom(type))
@@ -359,13 +418,20 @@ namespace Godot.Bridge
{
foreach (var type in scriptTypes)
{
- if (type.IsGenericType)
- continue;
-
LookupScriptForClass(type);
}
}
}
+
+ // This method may be called before initialization.
+ if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint())
+ {
+ foreach (var scriptPath in _pathTypeBiMap.Paths)
+ {
+ using godot_string nativeScriptPath = Marshaling.ConvertStringToNative(scriptPath);
+ NativeFuncs.godotsharp_internal_editor_file_system_update_file(nativeScriptPath);
+ }
+ }
}
[UnmanagedCallersOnly]
@@ -419,20 +485,8 @@ namespace Godot.Bridge
{
try
{
- lock (_scriptTypeBiMap.ReadWriteLock)
- {
- if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
- {
- string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
-
- if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
- return godot_bool.False;
-
- _scriptTypeBiMap.Add(scriptPtr, scriptType);
- }
- }
-
- return godot_bool.True;
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
+ return AddScriptBridgeCore(scriptPtr, scriptPathStr).ToGodotBool();
}
catch (Exception e)
{
@@ -441,6 +495,22 @@ namespace Godot.Bridge
}
}
+ private static unsafe bool AddScriptBridgeCore(IntPtr scriptPtr, string scriptPath)
+ {
+ lock (_scriptTypeBiMap.ReadWriteLock)
+ {
+ if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
+ {
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPath, out Type? scriptType))
+ return false;
+
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
+ }
+ }
+
+ return true;
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript)
{
@@ -452,6 +522,8 @@ namespace Godot.Bridge
return;
}
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Cannot get or create script for a generic type definition '{scriptType.FullName}'. Path: '{scriptPathStr}'.");
+
GetOrCreateScriptBridgeForType(scriptType, outScript);
}
@@ -491,16 +563,51 @@ namespace Godot.Bridge
if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
return true;
+ if (scriptType.IsConstructedGenericType)
+ {
+ // If the script type is generic, also try looking for the path of the generic type definition
+ // since we can use it to create the script.
+ Type genericTypeDefinition = scriptType.GetGenericTypeDefinition();
+ if (_pathTypeBiMap.TryGetGenericTypeDefinitionPath(genericTypeDefinition, out scriptPath))
+ return true;
+ }
+
CreateScriptBridgeForType(scriptType, outScript);
scriptPath = null;
return false;
}
}
+ static string GetVirtualConstructedGenericTypeScriptPath(Type scriptType, string scriptPath)
+ {
+ // Constructed generic types all have the same path which is not allowed by Godot
+ // (every Resource must have a unique path). So we create a unique "virtual" path
+ // for each type.
+
+ if (!scriptPath.StartsWith("res://"))
+ {
+ throw new ArgumentException("Script path must start with 'res://'.", nameof(scriptPath));
+ }
+
+ scriptPath = scriptPath.Substring("res://".Length);
+ return $"csharp://{scriptPath}:{scriptType}.cs";
+ }
+
if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
{
// This path is slower, but it's only executed for the first instantiation of the type
+ if (scriptType.IsConstructedGenericType && !scriptPath.StartsWith("csharp://"))
+ {
+ // If the script type is generic it can't be loaded using the real script path.
+ // Construct a virtual path unique to this constructed generic type and add it
+ // to the path bimap so they can be found later by their virtual path.
+ // IMPORTANT: The virtual path must be added to _pathTypeBiMap before the first
+ // load of the script, otherwise the loaded script won't be added to _scriptTypeBiMap.
+ scriptPath = GetVirtualConstructedGenericTypeScriptPath(scriptType, scriptPath);
+ _pathTypeBiMap.Add(scriptPath, scriptType);
+ }
+
// This must be done outside the read-write lock, as the script resource loading can lock it
using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
@@ -511,11 +618,23 @@ namespace Godot.Bridge
// with no path, as we do for types without an associated script file.
GetOrCreateScriptBridgeForType(scriptType, outScript);
}
+
+ if (scriptType.IsConstructedGenericType)
+ {
+ // When reloading generic scripts they won't be added to the script bimap because their
+ // virtual path won't be in the path bimap yet. The current method executes when a derived type
+ // is trying to get or create the script for their base type. The code above has now added
+ // the virtual path to the path bimap and loading the script with that path should retrieve
+ // any existing script, so now we have a chance to make sure it's added to the script bimap.
+ AddScriptBridgeCore(outScript->Reference, scriptPath);
+ }
}
}
private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
{
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
+
NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
IntPtr scriptPtr = outScript->Reference;
@@ -602,45 +721,82 @@ namespace Godot.Bridge
}
}
- [UnmanagedCallersOnly]
- internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName,
- godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath,
- godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
- godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
+ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo)
{
- try
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
+
+ string typeName = scriptType.Name;
+ if (scriptType.IsGenericType)
{
- // Performance is not critical here as this will be replaced with source generators.
- var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+ var sb = new StringBuilder();
+ AppendTypeName(sb, scriptType);
+ typeName = sb.ToString();
+ }
- *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
+ godot_string className = Marshaling.ConvertStringToNative(typeName);
- *outTool = scriptType.GetCustomAttributes(inherit: false)
- .OfType<ToolAttribute>()
- .Any().ToGodotBool();
+ bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
- if (!(*outTool).ToBool() && scriptType.IsNested)
- {
- *outTool = (scriptType.DeclaringType?.GetCustomAttributes(inherit: false)
- .OfType<ToolAttribute>()
- .Any() ?? false).ToGodotBool();
- }
+ // If the type is nested and the parent type is a tool script,
+ // consider the nested type a tool script as well.
+ if (!isTool && scriptType.IsNested)
+ {
+ isTool = scriptType.DeclaringType?.IsDefined(typeof(ToolAttribute), inherit: false) ?? false;
+ }
- if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
- *outTool = godot_bool.True;
+ // Every script in the GodotTools assembly is a tool script.
+ if (!isTool && scriptType.Assembly.GetName().Name == "GodotTools")
+ {
+ isTool = true;
+ }
- var globalAttr = scriptType.GetCustomAttributes(inherit: false)
- .OfType<GlobalClassAttribute>()
- .FirstOrDefault();
+ bool isGlobalClass = scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
- *outGlobal = (globalAttr != null).ToGodotBool();
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ .OfType<IconAttribute>()
+ .FirstOrDefault();
- var iconAttr = scriptType.GetCustomAttributes(inherit: false)
- .OfType<IconAttribute>()
- .FirstOrDefault();
- *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+
+ outTypeInfo->ClassName = className;
+ outTypeInfo->IconPath = iconPath;
+ outTypeInfo->IsTool = isTool.ToGodotBool();
+ outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool();
+ outTypeInfo->IsAbstract = scriptType.IsAbstract.ToGodotBool();
+ outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
+ outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
+
+ static void AppendTypeName(StringBuilder sb, Type type)
+ {
+ sb.Append(type.Name);
+ if (type.IsGenericType)
+ {
+ sb.Append('<');
+ for (int i = 0; i < type.GenericTypeArguments.Length; i++)
+ {
+ Type typeArg = type.GenericTypeArguments[i];
+ AppendTypeName(sb, typeArg);
+ if (i != type.GenericTypeArguments.Length - 1)
+ {
+ sb.Append(", ");
+ }
+ }
+ sb.Append('>');
+ }
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_csharp_type_info* outTypeInfo,
+ godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
+ {
+ try
+ {
+ // Performance is not critical here as this will be replaced with source generators.
+ var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
- *outAbstract = scriptType.IsAbstract.ToGodotBool();
+ GetScriptTypeInfo(scriptType, outTypeInfo);
// Methods
@@ -662,6 +818,19 @@ namespace Godot.Bridge
methodInfo.Add("name", method.Name);
+ var returnVal = new Collections.Dictionary()
+ {
+ { "name", method.ReturnVal.Name },
+ { "type", (int)method.ReturnVal.Type },
+ { "usage", (int)method.ReturnVal.Usage }
+ };
+ if (method.ReturnVal.ClassName != null)
+ {
+ returnVal["class_name"] = method.ReturnVal.ClassName;
+ }
+
+ methodInfo.Add("return_val", returnVal);
+
var methodParams = new Collections.Array();
if (method.Arguments != null)
@@ -804,11 +973,7 @@ namespace Godot.Bridge
catch (Exception e)
{
ExceptionUtils.LogException(e);
- *outClassName = default;
- *outTool = godot_bool.False;
- *outGlobal = godot_bool.False;
- *outAbstract = godot_bool.False;
- *outIconPath = default;
+ *outTypeInfo = default;
*outMethodsDest = NativeFuncs.godotsharp_array_new();
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
*outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
index a58f6849ad..1ec1a75516 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -19,6 +20,8 @@ public static partial class ScriptManagerBridge
{
// TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"A generic type definition must never be added to the script type map. Type: {scriptType}.");
+
_scriptTypeMap.Add(scriptPtr, scriptType);
_typeScriptMap.Add(scriptType, scriptPtr);
@@ -61,6 +64,8 @@ public static partial class ScriptManagerBridge
private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new();
private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new();
+ public System.Collections.Generic.IEnumerable<string> Paths => _pathTypeMap.Keys;
+
public void Add(string scriptPath, Type scriptType)
{
_pathTypeMap.Add(scriptPath, scriptType);
@@ -83,10 +88,29 @@ public static partial class ScriptManagerBridge
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
- _pathTypeMap.TryGetValue(scriptPath, out scriptType);
+ // This must never return true for a generic type definition, we only consider script types
+ // the types that can be attached to a Node/Resource (non-generic or constructed generic types).
+ _pathTypeMap.TryGetValue(scriptPath, out scriptType) && !scriptType.IsGenericTypeDefinition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) =>
- _typePathMap.TryGetValue(scriptType, out scriptPath);
+ public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath)
+ {
+ if (scriptType.IsGenericTypeDefinition)
+ {
+ // This must never return true for a generic type definition, we only consider script types
+ // the types that can be attached to a Node/Resource (non-generic or constructed generic types).
+ scriptPath = null;
+ return false;
+ }
+
+ return _typePathMap.TryGetValue(scriptType, out scriptPath);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetGenericTypeDefinitionPath(Type genericTypeDefinition, [MaybeNullWhen(false)] out string scriptPath)
+ {
+ Debug.Assert(genericTypeDefinition.IsGenericTypeDefinition);
+ return _typePathMap.TryGetValue(genericTypeDefinition, out scriptPath);
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 405f280cbb..d08fad90db 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -561,7 +561,7 @@ namespace Godot.Collections
/// </summary>
/// <param name="from">The typed dictionary to convert.</param>
/// <returns>A new Godot Dictionary, or <see langword="null"/> if <see paramref="from"/> was null.</returns>
- [return: NotNullIfNotNull(nameof(from))]
+ [return: NotNullIfNotNull("from")]
public static explicit operator Dictionary?(Dictionary<TKey, TValue>? from)
{
return from?._underlyingDict;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
index 8f8e884b8c..84b2a04276 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
@@ -38,6 +38,8 @@ namespace Godot
{
if (NativePtr == IntPtr.Zero)
{
+ Debug.Assert(nativeCtor != null);
+
NativePtr = nativeCtor();
InteropUtils.TieManagedToUnmanaged(this, NativePtr,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index 537d863ef2..04b6c2e743 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -241,7 +241,7 @@ namespace Godot.NativeInterop
return variant->Type.ToString();
}
- internal static void ThrowIfNullPtr(IntPtr ptr, [CallerArgumentExpression(nameof(ptr))] string? paramName = null)
+ internal static void ThrowIfNullPtr(IntPtr ptr, [CallerArgumentExpression("ptr")] string? paramName = null)
{
if (ptr == IntPtr.Zero)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index d5d9404ed1..a1a011e0f0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -105,6 +105,61 @@ namespace Godot.NativeInterop
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ // ReSharper disable once InconsistentNaming
+ public ref struct godot_csharp_type_info
+ {
+ private godot_string _className;
+ private godot_string _iconPath;
+ private godot_bool _isTool;
+ private godot_bool _isGlobalClass;
+ private godot_bool _isAbstract;
+ private godot_bool _isConstructedGenericType;
+ private godot_bool _isGenericTypeDefinition;
+
+ public godot_string ClassName
+ {
+ readonly get => _className;
+ set => _className = value;
+ }
+
+ public godot_string IconPath
+ {
+ readonly get => _iconPath;
+ set => _iconPath = value;
+ }
+
+ public godot_bool IsTool
+ {
+ readonly get => _isTool;
+ set => _isTool = value;
+ }
+
+ public godot_bool IsGlobalClass
+ {
+ readonly get => _isGlobalClass;
+ set => _isGlobalClass = value;
+ }
+
+ public godot_bool IsAbstract
+ {
+ readonly get => _isAbstract;
+ set => _isAbstract = value;
+ }
+
+ public godot_bool IsConstructedGenericType
+ {
+ readonly get => _isConstructedGenericType;
+ set => _isConstructedGenericType = value;
+ }
+
+ public godot_bool IsGenericTypeDefinition
+ {
+ readonly get => _isGenericTypeDefinition;
+ set => _isGenericTypeDefinition = value;
+ }
+ }
+
[StructLayout(LayoutKind.Sequential, Pack = 8)]
// ReSharper disable once InconsistentNaming
public ref struct godot_variant
@@ -474,7 +529,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != IntPtr.Zero ? *((int*)_ptr - 1) : 0;
+ get => _ptr != IntPtr.Zero ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -725,7 +780,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -875,7 +930,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -939,7 +994,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -971,7 +1026,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -1003,7 +1058,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -1035,7 +1090,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -1067,7 +1122,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -1099,7 +1154,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
@@ -1131,7 +1186,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *((int*)_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index 2ab6904417..0af640533d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -138,7 +138,7 @@ namespace Godot
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <param name="from">The <see cref="NodePath"/> to convert.</param>
- [return: NotNullIfNotNull(nameof(from))]
+ [return: NotNullIfNotNull("from")]
public static implicit operator string?(NodePath? from) => from?.ToString();
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index 71a35ab809..cf4ac45a9f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -123,8 +123,8 @@ namespace Godot
public readonly bool Encloses(Rect2 b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
- b._position.X + b._size.X < _position.X + _size.X &&
- b._position.Y + b._size.Y < _position.Y + _size.Y;
+ b._position.X + b._size.X <= _position.X + _size.X &&
+ b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index ef7e9eacd8..58560df0c5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -113,8 +113,8 @@ namespace Godot
public readonly bool Encloses(Rect2I b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
- b._position.X + b._size.X < _position.X + _size.X &&
- b._position.Y + b._size.Y < _position.Y + _size.Y;
+ b._position.X + b._size.X <= _position.X + _size.X &&
+ b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index d53bb9f536..e89bbbd370 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Text;
@@ -220,7 +219,7 @@ namespace Godot
{
if (hasText)
{
- sb.Append(instance.Substring(indentStop, i - indentStop));
+ sb.Append(instance.AsSpan(indentStop, i - indentStop));
}
sb.Append('\n');
hasText = false;
@@ -252,7 +251,7 @@ namespace Godot
if (hasText)
{
- sb.Append(instance.Substring(indentStop, instance.Length - indentStop));
+ sb.Append(instance.AsSpan(indentStop, instance.Length - indentStop));
}
return sb.ToString();
@@ -323,7 +322,7 @@ namespace Godot
string slice = aux.GetSliceCharacter(' ', i);
if (slice.Length > 0)
{
- slice = char.ToUpper(slice[0]) + slice.Substring(1);
+ slice = char.ToUpperInvariant(slice[0]) + slice.Substring(1);
if (i > 0)
cap += " ";
cap += slice;
@@ -408,13 +407,13 @@ namespace Godot
bool shouldSplit = condA || condB || condC || canBreakNumberLetter || canBreakLetterNumber;
if (shouldSplit)
{
- newString += instance.Substring(startIndex, i - startIndex) + "_";
+ newString += string.Concat(instance.AsSpan(startIndex, i - startIndex), "_");
startIndex = i;
}
}
newString += instance.Substring(startIndex, instance.Length - startIndex);
- return lowerCase ? newString.ToLower() : newString;
+ return lowerCase ? newString.ToLowerInvariant() : newString;
}
/// <summary>
@@ -479,9 +478,9 @@ namespace Godot
return -1; // If this is empty, and the other one is not, then we're less... I think?
if (to[toIndex] == 0)
return 1; // Otherwise the other one is smaller..
- if (char.ToUpper(instance[instanceIndex]) < char.ToUpper(to[toIndex])) // More than
+ if (char.ToUpperInvariant(instance[instanceIndex]) < char.ToUpperInvariant(to[toIndex])) // More than
return -1;
- if (char.ToUpper(instance[instanceIndex]) > char.ToUpper(to[toIndex])) // Less than
+ if (char.ToUpperInvariant(instance[instanceIndex]) > char.ToUpperInvariant(to[toIndex])) // Less than
return 1;
instanceIndex++;
@@ -853,7 +852,7 @@ namespace Godot
else
{
sb.Append(prefix);
- sb.Append(instance.Substring(lineStart, i - lineStart + 1));
+ sb.Append(instance.AsSpan(lineStart, i - lineStart + 1));
}
lineStart = i + 1;
}
@@ -861,7 +860,7 @@ namespace Godot
if (lineStart != instance.Length)
{
sb.Append(prefix);
- sb.Append(instance.Substring(lineStart));
+ sb.Append(instance.AsSpan(lineStart));
}
return sb.ToString();
}
@@ -925,8 +924,8 @@ namespace Godot
if (!caseSensitive)
{
- char sourcec = char.ToLower(instance[source]);
- char targetc = char.ToLower(text[target]);
+ char sourcec = char.ToLowerInvariant(instance[source]);
+ char targetc = char.ToLowerInvariant(text[target]);
match = sourcec == targetc;
}
else
@@ -1234,7 +1233,7 @@ namespace Godot
return false;
if (caseSensitive)
return instance[0] == expr[0];
- return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) &&
+ return (char.ToUpperInvariant(instance[0]) == char.ToUpperInvariant(expr[0])) &&
ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
index 51e97e0fb8..21d9ada127 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
@@ -84,7 +84,7 @@ namespace Godot
/// Converts a <see cref="StringName"/> to a string.
/// </summary>
/// <param name="from">The <see cref="StringName"/> to convert.</param>
- [return: NotNullIfNotNull(nameof(from))]
+ [return: NotNullIfNotNull("from")]
public static implicit operator string?(StringName? from) => from?.ToString();
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
index 104e30981f..8960323754 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
@@ -124,6 +124,29 @@ namespace Godot
}
/// <summary>
+ /// Returns the squared distance between this vector and <paramref name="to"/>.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
+ public readonly int DistanceSquaredTo(Vector2I to)
+ {
+ return (to - this).LengthSquared();
+ }
+
+ /// <summary>
+ /// Returns the distance between this vector and <paramref name="to"/>.
+ /// </summary>
+ /// <seealso cref="DistanceSquaredTo(Vector2I)"/>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
+ public readonly real_t DistanceTo(Vector2I to)
+ {
+ return (to - this).Length();
+ }
+
+ /// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
index 4af9fd878b..2d7bbc926d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
@@ -132,6 +132,29 @@ namespace Godot
}
/// <summary>
+ /// Returns the squared distance between this vector and <paramref name="to"/>.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
+ public readonly int DistanceSquaredTo(Vector3I to)
+ {
+ return (to - this).LengthSquared();
+ }
+
+ /// <summary>
+ /// Returns the distance between this vector and <paramref name="to"/>.
+ /// </summary>
+ /// <seealso cref="DistanceSquaredTo(Vector3I)"/>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
+ public readonly real_t DistanceTo(Vector3I to)
+ {
+ return (to - this).Length();
+ }
+
+ /// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
index 7d4d3dd353..9a85f359d7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
@@ -149,6 +149,29 @@ namespace Godot
}
/// <summary>
+ /// Returns the squared distance between this vector and <paramref name="to"/>.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
+ public readonly int DistanceSquaredTo(Vector4I to)
+ {
+ return (to - this).LengthSquared();
+ }
+
+ /// <summary>
+ /// Returns the distance between this vector and <paramref name="to"/>.
+ /// </summary>
+ /// <seealso cref="DistanceSquaredTo(Vector4I)"/>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
+ public readonly real_t DistanceTo(Vector4I to)
+ {
+ return (to - this).Length();
+ }
+
+ /// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 3518507f8c..0089e9c2a2 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -316,7 +316,7 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) {
}
void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) {
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 3eb746677d..0e34616951 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -53,13 +53,6 @@
#include <dlfcn.h>
#endif
-// TODO mobile
-#if 0
-#ifdef IOS_ENABLED
-#include "support/ios_support.h"
-#endif
-#endif
-
GDMono *GDMono::singleton = nullptr;
namespace {
@@ -395,6 +388,7 @@ void GDMono::initialize() {
if (godot_plugins_initialize != nullptr) {
is_native_aot = true;
+ runtime_initialized = true;
} else {
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
}
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index ef4e32e4a7..145f4cee90 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -59,6 +59,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptNativeName);
+ CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetGlobalClassName);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SetGodotObjectPtr);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index dac8cdcaef..46e9ab10cb 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -84,6 +84,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *);
+ using FuncScriptManagerBridge_GetGlobalClassName = void(GD_CLR_STDCALL *)(const String *, String *, String *, String *);
using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *);
using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *);
using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);
@@ -91,7 +92,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
- using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
+ using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, CSharpScript::TypeInfo *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
@@ -120,6 +121,7 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
FuncScriptManagerBridge_GetScriptNativeName ScriptManagerBridge_GetScriptNativeName;
+ FuncScriptManagerBridge_GetGlobalClassName ScriptManagerBridge_GetGlobalClassName;
FuncScriptManagerBridge_SetGodotObjectPtr ScriptManagerBridge_SetGodotObjectPtr;
FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal;
FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits;
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index aa97534675..ee17a668d7 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -152,7 +152,7 @@ String realpath(const String &p_path) {
}
return result.simplify_path();
-#elif UNIX_ENABLED
+#elif defined(UNIX_ENABLED)
char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr);
if (!resolved_path) {
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index a53eefc452..a45e5ffdc0 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -31,9 +31,9 @@
#include "editor_network_profiler.h"
#include "core/os/os.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
+#include "editor/themes/editor_scale.h"
void EditorNetworkProfiler::_bind_methods() {
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index eab1f5d51d..2e3df732e2 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -33,13 +33,13 @@
#include "../multiplayer_synchronizer.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
#include "editor/property_selector.h"
+#include "editor/themes/editor_scale.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
index a4d2aed2d6..c816bd3b6b 100644
--- a/modules/multiplayer/multiplayer_debugger.cpp
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -89,7 +89,7 @@ Error MultiplayerDebugger::_capture(void *p_user, const String &p_msg, const Arr
// BandwidthProfiler
int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
- ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
+ ERR_FAIL_COND_V(p_buffer.is_empty(), 0);
int total_bandwidth = 0;
uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
@@ -174,7 +174,7 @@ Array MultiplayerDebugger::RPCFrame::serialize() {
}
bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
- ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ ERR_FAIL_COND_V(p_arr.is_empty(), false);
uint32_t size = p_arr[0];
ERR_FAIL_COND_V(size % 6, false);
ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
@@ -279,7 +279,7 @@ Array MultiplayerDebugger::ReplicationFrame::serialize() {
}
bool MultiplayerDebugger::ReplicationFrame::deserialize(const Array &p_arr) {
- ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ ERR_FAIL_COND_V(p_arr.is_empty(), false);
uint32_t size = p_arr[0];
ERR_FAIL_COND_V(size % 7, false);
ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 264a2e9c8e..fecefc7e71 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -87,8 +87,8 @@ void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const {
}
#endif
-PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+Array MultiplayerSpawner::get_configuration_warnings() const {
+ Array warnings = Node::get_configuration_warnings();
if (spawn_path.is_empty() || !has_node(spawn_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes."));
diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h
index 0e94b781ea..6cd2946df7 100644
--- a/modules/multiplayer/multiplayer_spawner.h
+++ b/modules/multiplayer/multiplayer_spawner.h
@@ -91,7 +91,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
#endif
public:
- PackedStringArray get_configuration_warnings() const override;
+ Array get_configuration_warnings() const override;
Node *get_spawn_node() const {
return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr;
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 12b4ac540d..16df9da78e 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -49,11 +49,11 @@ void MultiplayerSynchronizer::_stop() {
}
#endif
root_node_cache = ObjectID();
- reset();
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->object_configuration_remove(node, this);
}
+ reset();
}
void MultiplayerSynchronizer::_start() {
@@ -143,8 +143,8 @@ bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time)
return true;
}
-PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+Array MultiplayerSynchronizer::get_configuration_warnings() const {
+ Array warnings = Node::get_configuration_warnings();
if (root_path.is_empty() || !has_node(root_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Root Path\" property in order for MultiplayerSynchronizer to be able to synchronize properties."));
diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h
index 192d7a5920..f5f3993f0d 100644
--- a/modules/multiplayer/multiplayer_synchronizer.h
+++ b/modules/multiplayer/multiplayer_synchronizer.h
@@ -91,7 +91,7 @@ public:
bool update_outbound_sync_time(uint64_t p_usec);
bool update_inbound_sync_time(uint16_t p_network_time);
- PackedStringArray get_configuration_warnings() const override;
+ Array get_configuration_warnings() const override;
void set_replication_interval(double p_interval);
double get_replication_interval() const;
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index 56cd0bec18..33b05d4cc2 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -35,25 +35,61 @@
#include "core/io/marshalls.h"
#include "scene/main/node.h"
#include "scene/main/window.h"
+#include "scene/scene_string_names.h"
+
+SceneCacheInterface::NodeCache &SceneCacheInterface::_track(Node *p_node) {
+ const ObjectID oid = p_node->get_instance_id();
+ NodeCache *nc = nodes_cache.getptr(oid);
+ if (!nc) {
+ nodes_cache[oid] = NodeCache();
+ p_node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneCacheInterface::_remove_node_cache).bind(oid), Object::CONNECT_ONE_SHOT);
+ }
+ return nodes_cache[oid];
+}
+
+void SceneCacheInterface::_remove_node_cache(ObjectID p_oid) {
+ NodeCache *nc = nodes_cache.getptr(p_oid);
+ if (!nc) {
+ return;
+ }
+ for (KeyValue<int, int> &E : nc->recv_ids) {
+ PeerInfo *pinfo = peers_info.getptr(E.key);
+ ERR_CONTINUE(!pinfo);
+ pinfo->recv_nodes.erase(E.value);
+ }
+ for (KeyValue<int, bool> &E : nc->confirmed_peers) {
+ PeerInfo *pinfo = peers_info.getptr(E.key);
+ ERR_CONTINUE(!pinfo);
+ pinfo->sent_nodes.erase(p_oid);
+ }
+ nodes_cache.erase(p_oid);
+}
void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
if (p_connected) {
- path_get_cache.insert(p_id, PathGetCache());
+ peers_info.insert(p_id, PeerInfo());
} else {
- // Cleanup get cache.
- path_get_cache.erase(p_id);
- // Cleanup sent cache.
- // Some refactoring is needed to make this faster and do paths GC.
- for (KeyValue<ObjectID, PathSentCache> &E : path_send_cache) {
- E.value.confirmed_peers.erase(p_id);
+ PeerInfo *pinfo = peers_info.getptr(p_id);
+ ERR_FAIL_NULL(pinfo); // Bug.
+ for (KeyValue<int, ObjectID> E : pinfo->recv_nodes) {
+ NodeCache *nc = nodes_cache.getptr(E.value);
+ ERR_CONTINUE(!nc);
+ nc->recv_ids.erase(E.key);
+ }
+ for (const ObjectID &oid : pinfo->sent_nodes) {
+ NodeCache *nc = nodes_cache.getptr(oid);
+ ERR_CONTINUE(!nc);
+ nc->confirmed_peers.erase(p_id);
}
+ peers_info.erase(p_id);
}
}
void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
+ ERR_FAIL_COND(!peers_info.has(p_from)); // Bug.
+ ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small.");
Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
ERR_FAIL_NULL(root_node);
- ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small.");
int ofs = 1;
String methods_md5;
@@ -63,15 +99,13 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
int id = decode_uint32(&p_packet[ofs]);
ofs += 4;
+ ERR_FAIL_COND_MSG(peers_info[p_from].recv_nodes.has(id), vformat("Duplicate remote cache ID %d for peer %d", id, p_from));
+
String paths;
paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs);
const NodePath path = paths;
- if (!path_get_cache.has(p_from)) {
- path_get_cache[p_from] = PathGetCache();
- }
-
Node *node = root_node->get_node(path);
ERR_FAIL_NULL(node);
const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5;
@@ -79,10 +113,9 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path);
}
- PathGetCache::NodeInfo ni;
- ni.path = node->get_path();
-
- path_get_cache[p_from].nodes[id] = ni;
+ peers_info[p_from].recv_nodes.insert(id, node->get_instance_id());
+ NodeCache &cache = _track(node);
+ cache.recv_ids.insert(p_from, id);
// Encode path to send ack.
CharString pname = String(path).utf8();
@@ -122,15 +155,15 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack
Node *node = root_node->get_node(path);
ERR_FAIL_NULL(node);
- PathSentCache *psc = path_send_cache.getptr(node->get_instance_id());
- ERR_FAIL_NULL_MSG(psc, "Invalid packet received. Tries to confirm a path which was not found in cache.");
+ NodeCache *cache = nodes_cache.getptr(node->get_instance_id());
+ ERR_FAIL_NULL_MSG(cache, "Invalid packet received. Tries to confirm a node which was not requested.");
- HashMap<int, bool>::Iterator E = psc->confirmed_peers.find(p_from);
- ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path.");
- E->value = true;
+ bool *confirmed = cache->confirmed_peers.getptr(p_from);
+ ERR_FAIL_NULL_MSG(confirmed, "Invalid packet received. Tries to confirm a node which was not requested.");
+ *confirmed = true;
}
-Error SceneCacheInterface::_send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers) {
+Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodeCache &p_cache, const List<int> &p_peers) {
// Encode function name.
const CharString path = String(multiplayer->get_root_path().rel_path_to(p_node->get_path())).utf8();
const int path_len = encode_cstring(path.get_data(), nullptr);
@@ -148,7 +181,7 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, PathSentCache *psc,
ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]);
- ofs += encode_uint32(psc->id, &packet.write[ofs]);
+ ofs += encode_uint32(p_cache.cache_id, &packet.write[ofs]);
ofs += encode_cstring(path.get_data(), &packet.write[ofs]);
@@ -162,80 +195,74 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, PathSentCache *psc,
err = multiplayer->send_command(peer_id, packet.ptr(), packet.size());
ERR_FAIL_COND_V(err != OK, err);
// Insert into confirmed, but as false since it was not confirmed.
- psc->confirmed_peers.insert(peer_id, false);
+ p_cache.confirmed_peers.insert(peer_id, false);
+ ERR_CONTINUE(!peers_info.has(peer_id));
+ peers_info[peer_id].sent_nodes.insert(p_node->get_instance_id());
}
return err;
}
bool SceneCacheInterface::is_cache_confirmed(Node *p_node, int p_peer) {
ERR_FAIL_NULL_V(p_node, false);
- const PathSentCache *psc = path_send_cache.getptr(p_node->get_instance_id());
- ERR_FAIL_NULL_V(psc, false);
- HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer);
- ERR_FAIL_COND_V(!F, false); // Should never happen.
- return F->value;
+ const ObjectID oid = p_node->get_instance_id();
+ NodeCache *cache = nodes_cache.getptr(oid);
+ bool *confirmed = cache ? cache->confirmed_peers.getptr(p_peer) : nullptr;
+ return confirmed && *confirmed;
}
int SceneCacheInterface::make_object_cache(Object *p_obj) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_NULL_V(node, -1);
- const ObjectID oid = node->get_instance_id();
- // See if the path is cached.
- PathSentCache *psc = path_send_cache.getptr(oid);
- if (!psc) {
- // Path is not cached, create.
- path_send_cache[oid] = PathSentCache();
- psc = path_send_cache.getptr(oid);
- psc->id = last_send_cache_id++;
+ NodeCache &cache = _track(node);
+ if (cache.cache_id == 0) {
+ cache.cache_id = last_send_cache_id++;
}
- return psc->id;
+ return cache.cache_id;
}
bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_NULL_V(node, false);
- const ObjectID oid = node->get_instance_id();
// See if the path is cached.
- PathSentCache *psc = path_send_cache.getptr(oid);
- if (!psc) {
- // Path is not cached, create.
- path_send_cache[oid] = PathSentCache();
- psc = path_send_cache.getptr(oid);
- psc->id = last_send_cache_id++;
+ NodeCache &cache = _track(node);
+ if (cache.cache_id == 0) {
+ cache.cache_id = last_send_cache_id++;
}
- r_id = psc->id;
+ r_id = cache.cache_id;
bool has_all_peers = true;
List<int> peers_to_add; // If one is missing, take note to add it.
if (p_peer_id > 0) {
// Fast single peer check.
- HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(p_peer_id);
- if (!F) {
+ ERR_FAIL_COND_V_MSG(!peers_info.has(p_peer_id), false, "Peer doesn't exist: " + itos(p_peer_id));
+
+ bool *confirmed = cache.confirmed_peers.getptr(p_peer_id);
+ if (!confirmed) {
peers_to_add.push_back(p_peer_id); // Need to also be notified.
has_all_peers = false;
- } else if (!F->value) {
+ } else if (!(*confirmed)) {
has_all_peers = false;
}
} else {
// Long and painful.
- for (const int &E : multiplayer->get_connected_peers()) {
- if (p_peer_id < 0 && E == -p_peer_id) {
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ if (p_peer_id < 0 && E.key == -p_peer_id) {
continue; // Continue, excluded.
}
- HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(E);
- if (!F) {
- peers_to_add.push_back(E); // Need to also be notified.
+ bool *confirmed = cache.confirmed_peers.getptr(E.key);
+ if (!confirmed) {
+ peers_to_add.push_back(E.key); // Need to also be notified.
has_all_peers = false;
- } else if (!F->value) {
+ } else if (!(*confirmed)) {
has_all_peers = false;
}
}
}
if (peers_to_add.size()) {
- _send_confirm_path(node, psc, peers_to_add);
+ _send_confirm_path(node, cache, peers_to_add);
}
return has_all_peers;
@@ -244,22 +271,23 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r
Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) {
Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
ERR_FAIL_NULL_V(root_node, nullptr);
- HashMap<int, PathGetCache>::Iterator E = path_get_cache.find(p_from);
- ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from));
-
- HashMap<int, PathGetCache::NodeInfo>::Iterator F = E->value.nodes.find(p_cache_id);
- ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from));
+ PeerInfo *pinfo = peers_info.getptr(p_from);
+ ERR_FAIL_NULL_V(pinfo, nullptr);
- PathGetCache::NodeInfo *ni = &F->value;
- Node *node = root_node->get_node(ni->path);
- if (!node) {
- ERR_PRINT("Failed to get cached path: " + String(ni->path) + ".");
- }
+ const ObjectID *oid = pinfo->recv_nodes.getptr(p_cache_id);
+ ERR_FAIL_NULL_V_MSG(oid, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from));
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid));
+ ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to get cached node from peer %d with cache ID %d.", p_from, p_cache_id));
return node;
}
void SceneCacheInterface::clear() {
- path_get_cache.clear();
- path_send_cache.clear();
+ for (KeyValue<ObjectID, NodeCache> &E : nodes_cache) {
+ Object *obj = ObjectDB::get_instance(E.key);
+ ERR_CONTINUE(!obj);
+ obj->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneCacheInterface::_remove_node_cache));
+ }
+ peers_info.clear();
+ nodes_cache.clear();
last_send_cache_id = 1;
}
diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h
index e63beb5f84..ab4a20c078 100644
--- a/modules/multiplayer/scene_cache_interface.h
+++ b/modules/multiplayer/scene_cache_interface.h
@@ -43,27 +43,26 @@ private:
SceneMultiplayer *multiplayer = nullptr;
//path sent caches
- struct PathSentCache {
- HashMap<int, bool> confirmed_peers;
- int id;
+ struct NodeCache {
+ int cache_id;
+ HashMap<int, int> recv_ids; // peer id, remote cache id
+ HashMap<int, bool> confirmed_peers; // peer id, confirmed
};
- //path get caches
- struct PathGetCache {
- struct NodeInfo {
- NodePath path;
- ObjectID instance;
- };
-
- HashMap<int, NodeInfo> nodes;
+ struct PeerInfo {
+ HashMap<int, ObjectID> recv_nodes; // remote cache id, ObjectID
+ HashSet<ObjectID> sent_nodes;
};
- HashMap<ObjectID, PathSentCache> path_send_cache;
- HashMap<int, PathGetCache> path_get_cache;
+ HashMap<ObjectID, NodeCache> nodes_cache;
+ HashMap<int, PeerInfo> peers_info;
int last_send_cache_id = 1;
+ void _remove_node_cache(ObjectID p_oid);
+ NodeCache &_track(Node *p_node);
+
protected:
- Error _send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers);
+ Error _send_confirm_path(Node *p_node, NodeCache &p_cache, const List<int> &p_peers);
public:
void clear();
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 665b246bc5..5655467df7 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -440,7 +440,7 @@ void SceneMultiplayer::disconnect_peer(int p_id) {
}
Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
- ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet.");
+ ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_DATA, "Trying to send an empty raw packet.");
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected.");
@@ -460,7 +460,7 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer
Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) {
ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_data.is_empty(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent.");
ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent.");
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index c95e4ff9c9..b61cf0bf1d 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -189,9 +189,6 @@ void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) {
spawned_nodes.insert(oid);
if (_has_authority(spawner)) {
- if (tobj.net_id == 0) {
- tobj.net_id = ++last_net_id;
- }
_update_spawn_visibility(0, oid);
}
}
@@ -249,6 +246,7 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
uint32_t net_id = pending_sync_net_ids[0];
pending_sync_net_ids.pop_front();
peers_info[pending_spawn_remote].recv_sync_ids[net_id] = sync->get_instance_id();
+ sync->set_net_id(net_id);
// Try to apply spawn state (before ready).
if (pending_buffer_size > 0) {
@@ -472,9 +470,13 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa
ERR_FAIL_COND_V(!multiplayer || !p_node || !p_spawner, ERR_BUG);
const ObjectID oid = p_node->get_instance_id();
- const TrackedNode *tnode = tracked_nodes.getptr(oid);
+ TrackedNode *tnode = tracked_nodes.getptr(oid);
ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER);
+ if (tnode->net_id == 0) {
+ // Ensure the node has an ID.
+ tnode->net_id = ++last_net_id;
+ }
uint32_t nid = tnode->net_id;
ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED);
@@ -707,7 +709,7 @@ MultiplayerSynchronizer *SceneReplicationInterface::_find_synchronizer(int p_pee
return sync;
}
-void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID> p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> p_last_watch_usecs) {
+void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> &p_last_watch_usecs) {
MAKE_ROOM(/* header */ 1 + /* element */ 4 + 8 + 4 + delta_mtu);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC | (1 << SceneMultiplayer::CMD_FLAG_0_SHIFT);
@@ -781,7 +783,7 @@ Error SceneReplicationInterface::on_delta_receive(int p_from, const uint8_t *p_b
ERR_CONTINUE_MSG(true, "Ignoring delta for non-authority or invalid synchronizer.");
}
List<NodePath> props = sync->get_delta_properties(indexes);
- ERR_FAIL_COND_V(props.size() == 0, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(props.is_empty(), ERR_INVALID_DATA);
Vector<Variant> vars;
vars.resize(props.size());
int consumed = 0;
@@ -799,7 +801,7 @@ Error SceneReplicationInterface::on_delta_receive(int p_from, const uint8_t *p_b
return OK;
}
-void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec) {
+void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec) {
MAKE_ROOM(/* header */ 3 + /* element */ 4 + 4 + sync_mtu);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC;
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index 3b3ec6a9ef..31211bb108 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -101,8 +101,8 @@ private:
bool _verify_synchronizer(int p_peer, MultiplayerSynchronizer *p_sync, uint32_t &r_net_id);
MultiplayerSynchronizer *_find_synchronizer(int p_peer, uint32_t p_net_ida);
- void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec);
- void _send_delta(int p_peer, const HashSet<ObjectID> p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> p_last_watch_usecs);
+ void _send_sync(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec);
+ void _send_delta(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> &p_last_watch_usecs);
Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len);
Error _make_despawn_packet(Node *p_node, int &r_len);
Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 5a27f315b9..14c72f3db4 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -180,6 +180,20 @@ real_t GodotNavigationServer::map_get_cell_height(RID p_map) const {
return map->get_cell_height();
}
+COMMAND_2(map_set_merge_rasterizer_cell_scale, RID, p_map, float, p_value) {
+ NavMap *map = map_owner.get_or_null(p_map);
+ ERR_FAIL_NULL(map);
+
+ map->set_merge_rasterizer_cell_scale(p_value);
+}
+
+float GodotNavigationServer::map_get_merge_rasterizer_cell_scale(RID p_map) const {
+ NavMap *map = map_owner.get_or_null(p_map);
+ ERR_FAIL_NULL_V(map, false);
+
+ return map->get_merge_rasterizer_cell_scale();
+}
+
COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) {
NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_NULL(map);
@@ -1116,6 +1130,10 @@ void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<Navig
#endif // _3D_DISABLED
}
+bool GodotNavigationServer::is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const {
+ return NavMeshGenerator3D::get_singleton()->is_baking(p_navigation_mesh);
+}
+
COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get_or_null(p_object);
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index f19450db27..f3bc1185d8 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -117,6 +117,9 @@ public:
COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height);
virtual real_t map_get_cell_height(RID p_map) const override;
+ COMMAND_2(map_set_merge_rasterizer_cell_scale, RID, p_map, float, p_value);
+ virtual float map_get_merge_rasterizer_cell_scale(RID p_map) const override;
+
COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled);
virtual bool map_get_use_edge_connections(RID p_map) const override;
@@ -258,6 +261,7 @@ public:
virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override;
COMMAND_1(free, RID, p_object);
diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp
index 76bfd3a101..5bd4a37fd7 100644
--- a/modules/navigation/godot_navigation_server_2d.cpp
+++ b/modules/navigation/godot_navigation_server_2d.cpp
@@ -221,6 +221,10 @@ void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<Nav
#endif // CLIPPER2_ENABLED
}
+bool GodotNavigationServer2D::is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const {
+ return NavMeshGenerator2D::get_singleton()->is_baking(p_navigation_polygon);
+}
+
GodotNavigationServer2D::GodotNavigationServer2D() {}
GodotNavigationServer2D::~GodotNavigationServer2D() {}
diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h
index 2f473da1ab..08f1730441 100644
--- a/modules/navigation/godot_navigation_server_2d.h
+++ b/modules/navigation/godot_navigation_server_2d.h
@@ -250,6 +250,7 @@ public:
virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override;
};
#endif // GODOT_NAVIGATION_SERVER_2D_H
diff --git a/modules/navigation/nav_agent.cpp b/modules/navigation/nav_agent.cpp
index cb219ac6c0..5a87ff6c4b 100644
--- a/modules/navigation/nav_agent.cpp
+++ b/modules/navigation/nav_agent.cpp
@@ -326,7 +326,7 @@ const Dictionary NavAgent::get_avoidance_data() const {
_avoidance_data["new_velocity"] = Vector3(rvo_agent_3d.newVelocity_.x(), rvo_agent_3d.newVelocity_.y(), rvo_agent_3d.newVelocity_.z());
_avoidance_data["velocity"] = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z());
_avoidance_data["position"] = Vector3(rvo_agent_3d.position_.x(), rvo_agent_3d.position_.y(), rvo_agent_3d.position_.z());
- _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_3d.prefVelocity_.x(), rvo_agent_3d.prefVelocity_.y(), rvo_agent_3d.prefVelocity_.z());
+ _avoidance_data["preferred_velocity"] = Vector3(rvo_agent_3d.prefVelocity_.x(), rvo_agent_3d.prefVelocity_.y(), rvo_agent_3d.prefVelocity_.z());
_avoidance_data["radius"] = float(rvo_agent_3d.radius_);
_avoidance_data["time_horizon_agents"] = float(rvo_agent_3d.timeHorizon_);
_avoidance_data["time_horizon_obstacles"] = 0.0;
@@ -341,7 +341,7 @@ const Dictionary NavAgent::get_avoidance_data() const {
_avoidance_data["new_velocity"] = Vector3(rvo_agent_2d.newVelocity_.x(), 0.0, rvo_agent_2d.newVelocity_.y());
_avoidance_data["velocity"] = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y());
_avoidance_data["position"] = Vector3(rvo_agent_2d.position_.x(), 0.0, rvo_agent_2d.position_.y());
- _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_2d.prefVelocity_.x(), 0.0, rvo_agent_2d.prefVelocity_.y());
+ _avoidance_data["preferred_velocity"] = Vector3(rvo_agent_2d.prefVelocity_.x(), 0.0, rvo_agent_2d.prefVelocity_.y());
_avoidance_data["radius"] = float(rvo_agent_2d.radius_);
_avoidance_data["time_horizon_agents"] = float(rvo_agent_2d.timeHorizon_);
_avoidance_data["time_horizon_obstacles"] = float(rvo_agent_2d.timeHorizonObst_);
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 6429513b53..9482da39ef 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -67,6 +67,7 @@ void NavMap::set_cell_size(real_t p_cell_size) {
return;
}
cell_size = p_cell_size;
+ _update_merge_rasterizer_cell_dimensions();
regenerate_polygons = true;
}
@@ -75,6 +76,16 @@ void NavMap::set_cell_height(real_t p_cell_height) {
return;
}
cell_height = p_cell_height;
+ _update_merge_rasterizer_cell_dimensions();
+ regenerate_polygons = true;
+}
+
+void NavMap::set_merge_rasterizer_cell_scale(float p_value) {
+ if (merge_rasterizer_cell_scale == p_value) {
+ return;
+ }
+ merge_rasterizer_cell_scale = p_value;
+ _update_merge_rasterizer_cell_dimensions();
regenerate_polygons = true;
}
@@ -103,9 +114,9 @@ void NavMap::set_link_connection_radius(real_t p_link_connection_radius) {
}
gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const {
- const int x = static_cast<int>(Math::floor(p_pos.x / cell_size));
- const int y = static_cast<int>(Math::floor(p_pos.y / cell_height));
- const int z = static_cast<int>(Math::floor(p_pos.z / cell_size));
+ const int x = static_cast<int>(Math::floor(p_pos.x / merge_rasterizer_cell_size));
+ const int y = static_cast<int>(Math::floor(p_pos.y / merge_rasterizer_cell_height));
+ const int z = static_cast<int>(Math::floor(p_pos.z / merge_rasterizer_cell_size));
gd::PointKey p;
p.key = 0;
@@ -116,7 +127,11 @@ gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const {
}
Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector<Vector3>(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector<Vector3>(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
+
// Clear metadata outputs.
if (r_path_types) {
r_path_types->clear();
@@ -576,7 +591,11 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
}
Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
+
bool use_collision = p_use_collision;
Vector3 closest_point;
real_t closest_point_d = FLT_MAX;
@@ -624,24 +643,35 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector
}
Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.point;
}
Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.normal;
}
RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, RID(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(RID(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.owner;
}
gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const {
+ RWLockRead read_lock(map_rwlock);
+
gd::ClosestPointQueryResult result;
real_t closest_point_ds = FLT_MAX;
@@ -770,6 +800,8 @@ void NavMap::remove_agent_as_controlled(NavAgent *agent) {
}
Vector3 NavMap::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ RWLockRead read_lock(map_rwlock);
+
const LocalVector<NavRegion *> map_regions = get_regions();
if (map_regions.is_empty()) {
@@ -834,6 +866,8 @@ Vector3 NavMap::get_random_point(uint32_t p_navigation_layers, bool p_uniformly)
}
void NavMap::sync() {
+ RWLockWrite write_lock(map_rwlock);
+
// Performance Monitor
int _new_pm_region_count = regions.size();
int _new_pm_agent_count = agents.size();
@@ -923,7 +957,7 @@ void NavMap::sync() {
connections[ek].push_back(new_connection);
} else {
// The edge is already connected with another edge, skip.
- ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'.");
+ ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001.");
}
}
}
@@ -1365,6 +1399,11 @@ void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys
}
}
+void NavMap::_update_merge_rasterizer_cell_dimensions() {
+ merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale;
+ merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
+}
+
NavMap::NavMap() {
avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads");
avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads");
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index e8cbe7e247..311a265e0c 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -48,6 +48,8 @@ class NavAgent;
class NavObstacle;
class NavMap : public NavRid {
+ RWLock map_rwlock;
+
/// Map Up
Vector3 up = Vector3(0, 1, 0);
@@ -56,6 +58,12 @@ class NavMap : public NavRid {
real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+ // For the inter-region merging to work, internal rasterization is performed.
+ float merge_rasterizer_cell_size = 0.25;
+ float merge_rasterizer_cell_height = 0.25;
+ // This value is used to control sensitivity of internal rasterizer.
+ float merge_rasterizer_cell_scale = 1.0;
+
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.
real_t edge_connection_margin = 0.25;
@@ -133,6 +141,11 @@ public:
void set_cell_height(real_t p_cell_height);
real_t get_cell_height() const { return cell_height; }
+ void set_merge_rasterizer_cell_scale(float p_value);
+ float get_merge_rasterizer_cell_scale() const {
+ return merge_rasterizer_cell_scale;
+ }
+
void set_use_edge_connections(bool p_enabled);
bool get_use_edge_connections() const {
return use_edge_connections;
@@ -217,6 +230,8 @@ private:
void _update_rvo_obstacles_tree_2d();
void _update_rvo_agents_tree_2d();
void _update_rvo_agents_tree_3d();
+
+ void _update_merge_rasterizer_cell_dimensions();
};
#endif // NAV_MAP_H
diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp
index 6dfafa4e91..0cb0648906 100644
--- a/modules/navigation/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/nav_mesh_generator_2d.cpp
@@ -157,11 +157,10 @@ void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -193,11 +192,10 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -212,6 +210,13 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
generator_task_mutex.unlock();
}
+bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) {
+ baking_navmesh_mutex.lock();
+ bool baking = baking_navmeshes.has(p_navigation_polygon);
+ baking_navmesh_mutex.unlock();
+ return baking;
+}
+
void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg);
@@ -738,9 +743,13 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
Paths64 traversable_polygon_paths;
Paths64 obstruction_polygon_paths;
+ traversable_polygon_paths.reserve(outline_count + traversable_outlines.size());
+ obstruction_polygon_paths.reserve(obstruction_outlines.size());
+
for (int i = 0; i < outline_count; i++) {
const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
Path64 subject_path;
+ subject_path.reserve(traversable_outline.size());
for (const Vector2 &traversable_point : traversable_outline) {
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
@@ -750,6 +759,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
Path64 subject_path;
+ subject_path.reserve(traversable_outline.size());
for (const Vector2 &traversable_point : traversable_outline) {
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
@@ -759,6 +769,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
Path64 clip_path;
+ clip_path.reserve(obstruction_outline.size());
for (const Vector2 &obstruction_point : obstruction_outline) {
const Point64 &point = Point64(obstruction_point.x, obstruction_point.y);
clip_path.push_back(point);
@@ -766,6 +777,22 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
obstruction_polygon_paths.push_back(clip_path);
}
+ Rect2 baking_rect = p_navigation_mesh->get_baking_rect();
+ if (baking_rect.has_area()) {
+ Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset();
+
+ const int rect_begin_x = baking_rect.position[0] + baking_rect_offset.x;
+ const int rect_begin_y = baking_rect.position[1] + baking_rect_offset.y;
+ const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x;
+ const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y;
+
+ Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
+ RectClip rect_clip = RectClip(clipper_rect);
+
+ traversable_polygon_paths = rect_clip.Execute(traversable_polygon_paths);
+ obstruction_polygon_paths = rect_clip.Execute(obstruction_polygon_paths);
+ }
+
Paths64 path_solution;
// first merge all traversable polygons according to user specified fill rule
@@ -782,6 +809,21 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
//path_solution = RamerDouglasPeucker(path_solution, 0.025); //
+ real_t border_size = p_navigation_mesh->get_border_size();
+ if (baking_rect.has_area() && border_size > 0.0) {
+ Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset();
+
+ const int rect_begin_x = baking_rect.position[0] + baking_rect_offset.x + border_size;
+ const int rect_begin_y = baking_rect.position[1] + baking_rect_offset.y + border_size;
+ const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x - border_size;
+ const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y - border_size;
+
+ Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
+ RectClip rect_clip = RectClip(clipper_rect);
+
+ path_solution = rect_clip.Execute(path_solution);
+ }
+
Vector<Vector<Vector2>> new_baked_outlines;
for (const Path64 &scaled_path : path_solution) {
@@ -799,6 +841,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
Paths64 polygon_paths;
+ polygon_paths.reserve(new_baked_outlines.size());
for (const Vector<Vector2> &baked_outline : new_baked_outlines) {
Path64 polygon_path;
diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h
index 763ad24636..4ec582bd51 100644
--- a/modules/navigation/nav_mesh_generator_2d.h
+++ b/modules/navigation/nav_mesh_generator_2d.h
@@ -92,6 +92,7 @@ public:
static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static bool is_baking(Ref<NavigationPolygon> p_navigation_polygon);
NavMeshGenerator2D();
~NavMeshGenerator2D();
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
index 8719801c72..95854f29e7 100644
--- a/modules/navigation/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -172,11 +172,10 @@ void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_na
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -208,12 +207,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
return;
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -228,6 +226,13 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
generator_task_mutex.unlock();
}
+bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) {
+ baking_navmesh_mutex.lock();
+ bool baking = baking_navmeshes.has(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+ return baking;
+}
+
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
@@ -625,6 +630,9 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
cfg.cs = p_navigation_mesh->get_cell_size();
cfg.ch = p_navigation_mesh->get_cell_height();
+ if (p_navigation_mesh->get_border_size() > 0.0) {
+ cfg.borderSize = (int)Math::ceil(p_navigation_mesh->get_border_size() / cfg.cs);
+ }
cfg.walkableSlopeAngle = p_navigation_mesh->get_agent_max_slope();
cfg.walkableHeight = (int)Math::ceil(p_navigation_mesh->get_agent_height() / cfg.ch);
cfg.walkableClimb = (int)Math::floor(p_navigation_mesh->get_agent_max_climb() / cfg.ch);
@@ -637,6 +645,9 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
cfg.detailSampleDist = MAX(p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance(), 0.1f);
cfg.detailSampleMaxError = p_navigation_mesh->get_cell_height() * p_navigation_mesh->get_detail_sample_max_error();
+ if (p_navigation_mesh->get_border_size() > 0.0 && !Math::is_equal_approx(p_navigation_mesh->get_cell_size(), p_navigation_mesh->get_border_size())) {
+ WARN_PRINT("Property border_size is ceiled to cell_size voxel units and loses precision.");
+ }
if (!Math::is_equal_approx((float)cfg.walkableHeight * cfg.ch, p_navigation_mesh->get_agent_height())) {
WARN_PRINT("Property agent_height is ceiled to cell_height voxel units and loses precision.");
}
@@ -702,7 +713,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
Vector<unsigned char> tri_areas;
tri_areas.resize(ntris);
- ERR_FAIL_COND(tri_areas.size() == 0);
+ ERR_FAIL_COND(tri_areas.is_empty());
memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
@@ -738,11 +749,11 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
- ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea));
} else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
- ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea));
} else {
- ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
+ ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea));
}
bake_state = "Creating contours..."; // step #8
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
index 4220927641..0251b02235 100644
--- a/modules/navigation/nav_mesh_generator_3d.h
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -99,6 +99,7 @@ public:
static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static bool is_baking(Ref<NavigationMesh> p_navigation_mesh);
NavMeshGenerator3D();
~NavMeshGenerator3D();
diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h
index aa5ccc96dc..175d08ca6d 100644
--- a/modules/navigation/nav_utils.h
+++ b/modules/navigation/nav_utils.h
@@ -138,7 +138,7 @@ struct NavigationPoly {
poly(p_poly) {}
bool operator==(const NavigationPoly &other) const {
- return this->poly == other.poly;
+ return poly == other.poly;
}
bool operator!=(const NavigationPoly &other) const {
diff --git a/modules/noise/editor/noise_editor_plugin.cpp b/modules/noise/editor/noise_editor_plugin.cpp
index 91e9f7d477..917fa0cd15 100644
--- a/modules/noise/editor/noise_editor_plugin.cpp
+++ b/modules/noise/editor/noise_editor_plugin.cpp
@@ -36,7 +36,7 @@
#include "../noise_texture_2d.h"
#include "editor/editor_inspector.h"
-#include "editor/editor_scale.h"
+#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/texture_rect.h"
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 1b0c5cb9e3..0443fec4a0 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -49,10 +49,6 @@ NoiseTexture2D::~NoiseTexture2D() {
}
void NoiseTexture2D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_update_texture"), &NoiseTexture2D::_update_texture);
- ClassDB::bind_method(D_METHOD("_generate_texture"), &NoiseTexture2D::_generate_texture);
- ClassDB::bind_method(D_METHOD("_thread_done", "image"), &NoiseTexture2D::_thread_done);
-
ClassDB::bind_method(D_METHOD("set_width", "width"), &NoiseTexture2D::set_width);
ClassDB::bind_method(D_METHOD("set_height", "height"), &NoiseTexture2D::set_height);
@@ -138,7 +134,7 @@ void NoiseTexture2D::_thread_done(const Ref<Image> &p_image) {
void NoiseTexture2D::_thread_function(void *p_ud) {
NoiseTexture2D *tex = static_cast<NoiseTexture2D *>(p_ud);
- tex->call_deferred(SNAME("_thread_done"), tex->_generate_texture());
+ callable_mp(tex, &NoiseTexture2D::_thread_done).call_deferred(tex->_generate_texture());
}
void NoiseTexture2D::_queue_update() {
@@ -147,7 +143,7 @@ void NoiseTexture2D::_queue_update() {
}
update_queued = true;
- call_deferred(SNAME("_update_texture"));
+ callable_mp(this, &NoiseTexture2D::_update_texture).call_deferred();
}
Ref<Image> NoiseTexture2D::_generate_texture() {
diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp
index 33e257a5c2..f2e01b0612 100644
--- a/modules/noise/noise_texture_3d.cpp
+++ b/modules/noise/noise_texture_3d.cpp
@@ -49,10 +49,6 @@ NoiseTexture3D::~NoiseTexture3D() {
}
void NoiseTexture3D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_update_texture"), &NoiseTexture3D::_update_texture);
- ClassDB::bind_method(D_METHOD("_generate_texture"), &NoiseTexture3D::_generate_texture);
- ClassDB::bind_method(D_METHOD("_thread_done", "image"), &NoiseTexture3D::_thread_done);
-
ClassDB::bind_method(D_METHOD("set_width", "width"), &NoiseTexture3D::set_width);
ClassDB::bind_method(D_METHOD("set_height", "height"), &NoiseTexture3D::set_height);
ClassDB::bind_method(D_METHOD("set_depth", "depth"), &NoiseTexture3D::set_depth);
@@ -126,7 +122,7 @@ void NoiseTexture3D::_thread_done(const TypedArray<Image> &p_data) {
void NoiseTexture3D::_thread_function(void *p_ud) {
NoiseTexture3D *tex = static_cast<NoiseTexture3D *>(p_ud);
- tex->call_deferred(SNAME("_thread_done"), tex->_generate_texture());
+ callable_mp(tex, &NoiseTexture3D::_thread_done).call_deferred(tex->_generate_texture());
}
void NoiseTexture3D::_queue_update() {
@@ -135,7 +131,7 @@ void NoiseTexture3D::_queue_update() {
}
update_queued = true;
- call_deferred(SNAME("_update_texture"));
+ callable_mp(this, &NoiseTexture3D::_update_texture).call_deferred();
}
TypedArray<Image> NoiseTexture3D::_generate_texture() {
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index c64b6de78c..77922045eb 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -24,6 +24,9 @@ elif env["platform"] == "linuxbsd":
if env["x11"]:
env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"])
+ if env["wayland"]:
+ env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_WAYLAND"])
+
# FIXME: Review what needs to be set for Android and macOS.
env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"])
elif env["platform"] == "windows":
@@ -93,35 +96,13 @@ if env["builtin_openxr"]:
module_obj = []
env_openxr.add_source_files(module_obj, "*.cpp")
-env_openxr.add_source_files(module_obj, "action_map/*.cpp")
-env_openxr.add_source_files(module_obj, "scene/*.cpp")
+env.modules_sources += module_obj
-# We're a little more targeted with our extensions
-if env["platform"] == "android":
- env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp")
-if env["vulkan"]:
- env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp")
-if env["opengl3"] and env["platform"] != "macos":
- env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp")
-
-env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_fb_foveation_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_fb_update_swapchain_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_ml2_controller_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_extension_wrapper_extension.cpp")
-env_openxr.add_source_files(module_obj, "extensions/openxr_api_extension.cpp")
+Export("env_openxr")
-env.modules_sources += module_obj
+SConscript("action_map/SCsub")
+SConscript("extensions/SCsub")
+SConscript("scene/SCsub")
if env.editor_build:
SConscript("editor/SCsub")
diff --git a/modules/openxr/action_map/SCsub b/modules/openxr/action_map/SCsub
new file mode 100644
index 0000000000..7a493011ec
--- /dev/null
+++ b/modules/openxr/action_map/SCsub
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_openxr")
+
+module_obj = []
+
+env_openxr.add_source_files(module_obj, "*.cpp")
+
+env.modules_sources += module_obj
diff --git a/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp
index df607b0def..75cfb095bb 100644
--- a/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp
@@ -360,6 +360,9 @@ void OpenXRInteractionProfileMetadata::_register_core_metadata() {
register_io_path("/interaction_profiles/oculus/touch_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
register_io_path("/interaction_profiles/oculus/touch_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ register_io_path("/interaction_profiles/oculus/touch_controller", "Thumbrest touch", "/user/hand/left", "/user/hand/left/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ register_io_path("/interaction_profiles/oculus/touch_controller", "Thumbrest touch", "/user/hand/right", "/user/hand/right/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
register_io_path("/interaction_profiles/oculus/touch_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
register_io_path("/interaction_profiles/oculus/touch_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
index e795311651..1bc3a1a3fc 100644
--- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
@@ -75,6 +75,12 @@
Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer.
</description>
</method>
+ <method name="is_environment_blend_mode_alpha_supported">
+ <return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" />
+ <description>
+ Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really support, emulated or not supported at all.
+ </description>
+ </method>
<method name="is_initialized">
<return type="bool" />
<description>
@@ -94,6 +100,20 @@
Returns [code]true[/code] if OpenXR is enabled.
</description>
</method>
+ <method name="register_composition_layer_provider">
+ <return type="void" />
+ <param index="0" name="extension" type="OpenXRExtensionWrapperExtension" />
+ <description>
+ Registers the given extension as a composition layer provider.
+ </description>
+ </method>
+ <method name="set_emulate_environment_blend_mode_alpha_blend">
+ <return type="void" />
+ <param index="0" name="enabled" type="bool" />
+ <description>
+ If set to [code]true[/code], an OpenXR extension is loaded which is capable of emulating the [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] blend mode.
+ </description>
+ </method>
<method name="transform_from_pose">
<return type="Transform3D" />
<param index="0" name="pose" type="const void*" />
@@ -101,6 +121,13 @@
Creates a [Transform3D] from an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html]XrPosef[/url].
</description>
</method>
+ <method name="unregister_composition_layer_provider">
+ <return type="void" />
+ <param index="0" name="extension" type="OpenXRExtensionWrapperExtension" />
+ <description>
+ Unregisters the given extension as a composition layer provider.
+ </description>
+ </method>
<method name="xr_result">
<return type="bool" />
<param index="0" name="result" type="int" />
@@ -111,4 +138,15 @@
</description>
</method>
</methods>
+ <constants>
+ <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE" value="0" enum="OpenXRAlphaBlendModeSupport">
+ Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] isn't supported at all.
+ </constant>
+ <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL" value="1" enum="OpenXRAlphaBlendModeSupport">
+ Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really supported.
+ </constant>
+ <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING" value="2" enum="OpenXRAlphaBlendModeSupport">
+ Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is emulated.
+ </constant>
+ </constants>
</class>
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
index e09b58e5b4..b923d9244d 100644
--- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -9,6 +9,12 @@
<tutorials>
</tutorials>
<methods>
+ <method name="_get_composition_layer" qualifiers="virtual">
+ <return type="int" />
+ <description>
+ Returns a pointer to a [code]XrCompositionLayerBaseHeader[/code] struct to provide a composition layer. This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider].
+ </description>
+ </method>
<method name="_get_requested_extensions" qualifiers="virtual">
<return type="Dictionary" />
<description>
@@ -123,6 +129,14 @@
Called when the OpenXR session state is changed to visible. This means OpenXR is now ready to receive frames.
</description>
</method>
+ <method name="_set_hand_joint_locations_and_get_next_pointer" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="hand_index" type="int" />
+ <param index="1" name="next_pointer" type="void*" />
+ <description>
+ Adds additional data structures when each hand tracker is created.
+ </description>
+ </method>
<method name="_set_instance_create_info_and_get_next_pointer" qualifiers="virtual">
<return type="int" />
<param index="0" name="next_pointer" type="void*" />
diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml
index cc7766507f..1c4da83138 100644
--- a/modules/openxr/doc_classes/OpenXRHand.xml
+++ b/modules/openxr/doc_classes/OpenXRHand.xml
@@ -1,14 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="OpenXRHand" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Node supporting finger tracking in OpenXR.
+ Node supporting hand and finger tracking in OpenXR.
</brief_description>
<description>
- This node enables OpenXR's hand tracking functionality. The node should be a child node of an [XROrigin3D] node, tracking will update its position to where the player's actual hand is positioned. This node also updates the skeleton of a properly skinned hand model. The hand mesh should be a child node of this node.
+ This node enables OpenXR's hand tracking functionality. The node should be a child node of an [XROrigin3D] node, tracking will update its position to the player's tracked hand Palm joint location (the center of the middle finger's metacarpal bone). This node also updates the skeleton of a properly skinned hand or avatar model.
+ If the skeleton is a hand (one of the hand bones is the root node of the skeleton), then the skeleton will be placed relative to the hand palm location and the hand mesh and skeleton should be children of the OpenXRHand node.
+ If the hand bones are part of a full skeleton, then the root of the hand will keep its location with the assumption that IK is used to position the hand and arm.
+ By default the skeleton hand bones are repositioned to match the size of the tracked hand. To preserve the modeled bone sizes change [member bone_update] to apply rotation only.
</description>
<tutorials>
</tutorials>
<members>
+ <member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="OpenXRHand.BoneUpdate" default="0">
+ Specify the type of updates to perform on the bone.
+ </member>
<member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0">
Specifies whether this node tracks the left or right hand of the player.
</member>
@@ -18,6 +24,9 @@
<member name="motion_range" type="int" setter="set_motion_range" getter="get_motion_range" enum="OpenXRHand.MotionRange" default="0">
Set the motion range (if supported) limiting the hand motion.
</member>
+ <member name="skeleton_rig" type="int" setter="set_skeleton_rig" getter="get_skeleton_rig" enum="OpenXRHand.SkeletonRig" default="0">
+ Set the type of skeleton rig the [member hand_skeleton] is compliant with.
+ </member>
</members>
<constants>
<constant name="HAND_LEFT" value="0" enum="Hands">
@@ -38,5 +47,23 @@
<constant name="MOTION_RANGE_MAX" value="2" enum="MotionRange">
Maximum supported motion ranges.
</constant>
+ <constant name="SKELETON_RIG_OPENXR" value="0" enum="SkeletonRig">
+ An OpenXR compliant skeleton.
+ </constant>
+ <constant name="SKELETON_RIG_HUMANOID" value="1" enum="SkeletonRig">
+ A [SkeletonProfileHumanoid] compliant skeleton.
+ </constant>
+ <constant name="SKELETON_RIG_MAX" value="2" enum="SkeletonRig">
+ Maximum supported hands.
+ </constant>
+ <constant name="BONE_UPDATE_FULL" value="0" enum="BoneUpdate">
+ The skeletons bones are fully updated (both position and rotation) to match the tracked bones.
+ </constant>
+ <constant name="BONE_UPDATE_ROTATION_ONLY" value="1" enum="BoneUpdate">
+ The skeletons bones are only rotated to align with the tracked bones, preserving bone length.
+ </constant>
+ <constant name="BONE_UPDATE_MAX" value="2" enum="BoneUpdate">
+ Maximum supported bone update mode.
+ </constant>
</constants>
</class>
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index 666d20d7e7..caaaeb90ff 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -71,6 +71,13 @@
If handtracking is enabled, returns the rotation of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR.
</description>
</method>
+ <method name="get_hand_tracking_source" qualifiers="const">
+ <return type="int" enum="OpenXRInterface.HandTrackedSource" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <description>
+ If handtracking is enabled and hand tracking source is supported, gets the source of the hand tracking data for [param hand].
+ </description>
+ </method>
<method name="get_motion_range" qualifiers="const">
<return type="int" enum="OpenXRInterface.HandMotionRange" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
@@ -177,10 +184,25 @@
Maximum value for the hand enum.
</constant>
<constant name="HAND_MOTION_RANGE_UNOBSTRUCTED" value="0" enum="HandMotionRange">
+ Full hand range, if user closes their hands, we make a full fist.
</constant>
<constant name="HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER" value="1" enum="HandMotionRange">
+ Conform to controller, if user closes their hands, the tracked data conforms to the shape of the controller.
</constant>
<constant name="HAND_MOTION_RANGE_MAX" value="2" enum="HandMotionRange">
+ Maximum value for the motion range enum.
+ </constant>
+ <constant name="HAND_TRACKED_SOURCE_UNKNOWN" value="0" enum="HandTrackedSource">
+ The source of hand tracking data is unknown (the extension is likely unsupported).
+ </constant>
+ <constant name="HAND_TRACKED_SOURCE_UNOBSTRUCTED" value="1" enum="HandTrackedSource">
+ The source of hand tracking is unobstructed, this means that an accurate method of hand tracking is used, e.g. optical hand tracking, data gloves, etc.
+ </constant>
+ <constant name="HAND_TRACKED_SOURCE_CONTROLLER" value="2" enum="HandTrackedSource">
+ The source of hand tracking is a controller, bone positions are inferred from controller inputs.
+ </constant>
+ <constant name="HAND_TRACKED_SOURCE_MAX" value="3" enum="HandTrackedSource">
+ Maximum value for the hand tracked source enum.
</constant>
<constant name="HAND_JOINT_PALM" value="0" enum="HandJoints">
Palm joint.
diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp
index 64e07eff21..15468a1c67 100644
--- a/modules/openxr/editor/openxr_action_map_editor.cpp
+++ b/modules/openxr/editor/openxr_action_map_editor.cpp
@@ -32,9 +32,9 @@
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/themes/editor_scale.h"
// TODO implement redo/undo system
@@ -43,7 +43,6 @@ void OpenXRActionMapEditor::_bind_methods() {
ClassDB::bind_method("_add_interaction_profile_editor", &OpenXRActionMapEditor::_add_interaction_profile_editor);
ClassDB::bind_method(D_METHOD("_add_action_set", "name"), &OpenXRActionMapEditor::_add_action_set);
- ClassDB::bind_method(D_METHOD("_set_focus_on_action_set", "action_set"), &OpenXRActionMapEditor::_set_focus_on_action_set);
ClassDB::bind_method(D_METHOD("_remove_action_set", "name"), &OpenXRActionMapEditor::_remove_action_set);
ClassDB::bind_method(D_METHOD("_do_add_action_set_editor", "action_set_editor"), &OpenXRActionMapEditor::_do_add_action_set_editor);
@@ -175,7 +174,7 @@ void OpenXRActionMapEditor::_on_add_action_set() {
// Make sure our action set is the current tab
tabs->set_current_tab(0);
- call_deferred("_set_focus_on_action_set", new_action_set_editor);
+ callable_mp(this, &OpenXRActionMapEditor::_set_focus_on_action_set).call_deferred(new_action_set_editor);
}
void OpenXRActionMapEditor::_set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor) {
diff --git a/modules/openxr/editor/openxr_editor_plugin.cpp b/modules/openxr/editor/openxr_editor_plugin.cpp
index 51ebbcf44a..559890ecb3 100644
--- a/modules/openxr/editor/openxr_editor_plugin.cpp
+++ b/modules/openxr/editor/openxr_editor_plugin.cpp
@@ -53,6 +53,11 @@ void OpenXREditorPlugin::make_visible(bool p_visible) {
OpenXREditorPlugin::OpenXREditorPlugin() {
action_map_editor = memnew(OpenXRActionMapEditor);
EditorNode::get_singleton()->add_bottom_panel_item(TTR("OpenXR Action Map"), action_map_editor);
+
+#ifndef ANDROID_ENABLED
+ select_runtime = memnew(OpenXRSelectRuntime);
+ add_control_to_container(CONTAINER_TOOLBAR, select_runtime);
+#endif
}
OpenXREditorPlugin::~OpenXREditorPlugin() {
diff --git a/modules/openxr/editor/openxr_editor_plugin.h b/modules/openxr/editor/openxr_editor_plugin.h
index 9764f8fe21..b80f20d049 100644
--- a/modules/openxr/editor/openxr_editor_plugin.h
+++ b/modules/openxr/editor/openxr_editor_plugin.h
@@ -32,6 +32,7 @@
#define OPENXR_EDITOR_PLUGIN_H
#include "openxr_action_map_editor.h"
+#include "openxr_select_runtime.h"
#include "editor/editor_plugin.h"
@@ -39,6 +40,9 @@ class OpenXREditorPlugin : public EditorPlugin {
GDCLASS(OpenXREditorPlugin, EditorPlugin);
OpenXRActionMapEditor *action_map_editor = nullptr;
+#ifndef ANDROID_ENABLED
+ OpenXRSelectRuntime *select_runtime = nullptr;
+#endif
public:
virtual String get_name() const override { return "OpenXRPlugin"; }
diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
index 08fcdaf771..51642d8503 100644
--- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
+++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
@@ -77,7 +77,7 @@ void OpenXRSelectInteractionProfileDialog::open(PackedStringArray p_do_not_inclu
// in with the new
PackedStringArray interaction_profiles = OpenXRInteractionProfileMetadata::get_singleton()->get_interaction_profile_paths();
for (int i = 0; i < interaction_profiles.size(); i++) {
- String path = interaction_profiles[i];
+ const String &path = interaction_profiles[i];
if (!p_do_not_include.has(path)) {
Button *ip_button = memnew(Button);
ip_button->set_flat(true);
diff --git a/modules/openxr/editor/openxr_select_runtime.cpp b/modules/openxr/editor/openxr_select_runtime.cpp
new file mode 100644
index 0000000000..f6aa157907
--- /dev/null
+++ b/modules/openxr/editor/openxr_select_runtime.cpp
@@ -0,0 +1,132 @@
+/**************************************************************************/
+/* openxr_select_runtime.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_select_runtime.h"
+
+#include "core/io/dir_access.h"
+#include "core/os/os.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
+
+void OpenXRSelectRuntime::_bind_methods() {
+}
+
+void OpenXRSelectRuntime::_update_items() {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ OS *os = OS::get_singleton();
+ Dictionary runtimes = EDITOR_GET("xr/openxr/runtime_paths");
+
+ int current_runtime = 0;
+ int index = 0;
+ String current_path = os->get_environment("XR_RUNTIME_JSON");
+
+ // Parse the user's home folder.
+ String home_folder = os->get_environment("HOME");
+ if (home_folder.is_empty()) {
+ home_folder = os->get_environment("HOMEDRIVE") + os->get_environment("HOMEPATH");
+ }
+
+ clear();
+ add_item("Default", -1);
+ set_item_metadata(index, "");
+ index++;
+
+ Array keys = runtimes.keys();
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys[i];
+ String path = runtimes[key];
+ String adj_path = path.replace("~", home_folder);
+
+ if (da->file_exists(adj_path)) {
+ add_item(key, index);
+ set_item_metadata(index, adj_path);
+
+ if (current_path == adj_path) {
+ current_runtime = index;
+ }
+ index++;
+ }
+ }
+
+ select(current_runtime);
+}
+
+void OpenXRSelectRuntime::_item_selected(int p_which) {
+ OS *os = OS::get_singleton();
+
+ if (p_which == 0) {
+ // Return to default runtime
+ os->set_environment("XR_RUNTIME_JSON", "");
+ } else {
+ // Select the runtime we want
+ String runtime_path = get_item_metadata(p_which);
+ os->set_environment("XR_RUNTIME_JSON", runtime_path);
+ }
+}
+
+void OpenXRSelectRuntime::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_ENTER_TREE: {
+ // Update dropdown
+ _update_items();
+
+ // Connect signal
+ connect("item_selected", callable_mp(this, &OpenXRSelectRuntime::_item_selected));
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ // Disconnect signal
+ disconnect("item_selected", callable_mp(this, &OpenXRSelectRuntime::_item_selected));
+ } break;
+ }
+}
+
+OpenXRSelectRuntime::OpenXRSelectRuntime() {
+ Dictionary default_runtimes;
+
+ // Add known common runtimes by default.
+#ifdef WINDOWS_ENABLED
+ default_runtimes["Meta"] = "C:\\Program Files\\Oculus\\Support\\oculus-runtime\\oculus_openxr_64.json";
+ default_runtimes["SteamVR"] = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\steamxr_win64.json";
+ default_runtimes["Varjo"] = "C:\\Program Files\\Varjo\\varjo-openxr\\VarjoOpenXR.json";
+ default_runtimes["WMR"] = "C:\\WINDOWS\\system32\\MixedRealityRuntime.json";
+#endif
+#ifdef LINUXBSD_ENABLED
+ default_runtimes["Monado"] = "/usr/share/openxr/1/openxr_monado.json";
+ default_runtimes["SteamVR"] = "~/.steam/steam/steamapps/common/SteamVR/steamxr_linux64.json";
+#endif
+
+ EDITOR_DEF_RST("xr/openxr/runtime_paths", default_runtimes);
+
+ set_flat(true);
+ set_theme_type_variation("TopBarOptionButton");
+ set_fit_to_longest_item(false);
+ set_focus_mode(Control::FOCUS_NONE);
+ set_tooltip_text(TTR("Choose an XR runtime."));
+}
diff --git a/modules/openxr/editor/openxr_select_runtime.h b/modules/openxr/editor/openxr_select_runtime.h
new file mode 100644
index 0000000000..60b5137f67
--- /dev/null
+++ b/modules/openxr/editor/openxr_select_runtime.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* openxr_select_runtime.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_SELECT_RUNTIME_H
+#define OPENXR_SELECT_RUNTIME_H
+
+#include "scene/gui/option_button.h"
+
+class OpenXRSelectRuntime : public OptionButton {
+ GDCLASS(OpenXRSelectRuntime, OptionButton);
+
+public:
+ OpenXRSelectRuntime();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_notification);
+
+private:
+ void _update_items();
+ void _item_selected(int p_which);
+};
+
+#endif // OPENXR_SELECT_RUNTIME_H
diff --git a/modules/openxr/extensions/SCsub b/modules/openxr/extensions/SCsub
new file mode 100644
index 0000000000..1bd9cfaa22
--- /dev/null
+++ b/modules/openxr/extensions/SCsub
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_openxr")
+
+module_obj = []
+
+env_openxr.add_source_files(module_obj, "*.cpp")
+
+# These are platform dependent
+if env["platform"] == "android":
+ env_openxr.add_source_files(module_obj, "platform/openxr_android_extension.cpp")
+if env["vulkan"]:
+ env_openxr.add_source_files(module_obj, "platform/openxr_vulkan_extension.cpp")
+if env["opengl3"] and env["platform"] != "macos":
+ env_openxr.add_source_files(module_obj, "platform/openxr_opengl_extension.cpp")
+
+env.modules_sources += module_obj
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index 31f8d23268..b9c9247bee 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -60,6 +60,7 @@ public:
virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } // Add additional data structures when we create our OpenXR instance.
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } // Add additional data structures when we create our OpenXR session.
virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } // Add additional data structures when creating OpenXR swap chains.
+ virtual void *set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) { return p_next_pointer; }
// `on_register_metadata` allows extensions to register additional controller metadata.
// This function is called even when OpenXRApi is not constructured as the metadata
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 4829f713d2..5ad7a97eca 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -38,6 +38,8 @@ void OpenXRExtensionWrapperExtension::_bind_methods() {
GDVIRTUAL_BIND(_set_instance_create_info_and_get_next_pointer, "next_pointer");
GDVIRTUAL_BIND(_set_session_create_and_get_next_pointer, "next_pointer");
GDVIRTUAL_BIND(_set_swapchain_create_info_and_get_next_pointer, "next_pointer");
+ GDVIRTUAL_BIND(_set_hand_joint_locations_and_get_next_pointer, "hand_index", "next_pointer");
+ GDVIRTUAL_BIND(_get_composition_layer);
GDVIRTUAL_BIND(_on_register_metadata);
GDVIRTUAL_BIND(_on_before_instance_created);
GDVIRTUAL_BIND(_on_instance_created, "instance");
@@ -117,6 +119,26 @@ void *OpenXRExtensionWrapperExtension::set_swapchain_create_info_and_get_next_po
return nullptr;
}
+void *OpenXRExtensionWrapperExtension::set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_set_hand_joint_locations_and_get_next_pointer, p_hand_index, GDExtensionPtr<void>(p_next_pointer), pointer)) {
+ return reinterpret_cast<void *>(pointer);
+ }
+
+ return nullptr;
+}
+
+XrCompositionLayerBaseHeader *OpenXRExtensionWrapperExtension::get_composition_layer() {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_get_composition_layer, pointer)) {
+ return reinterpret_cast<XrCompositionLayerBaseHeader *>(pointer);
+ }
+
+ return nullptr;
+}
+
void OpenXRExtensionWrapperExtension::on_register_metadata() {
GDVIRTUAL_CALL(_on_register_metadata);
}
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
index 5c5e64f927..4d8b19f4fd 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -39,7 +39,7 @@
#include "core/os/thread_safe.h"
#include "core/variant/native_ptr.h"
-class OpenXRExtensionWrapperExtension : public Object, OpenXRExtensionWrapper {
+class OpenXRExtensionWrapperExtension : public Object, public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider {
GDCLASS(OpenXRExtensionWrapperExtension, Object);
protected:
@@ -58,12 +58,16 @@ public:
virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) override;
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override;
+ virtual void *set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) override;
+ virtual XrCompositionLayerBaseHeader *get_composition_layer() override;
//TODO workaround as GDExtensionPtr<void> return type results in build error in godot-cpp
GDVIRTUAL1R(uint64_t, _set_system_properties_and_get_next_pointer, GDExtensionPtr<void>);
GDVIRTUAL1R(uint64_t, _set_instance_create_info_and_get_next_pointer, GDExtensionPtr<void>);
GDVIRTUAL1R(uint64_t, _set_session_create_and_get_next_pointer, GDExtensionPtr<void>);
GDVIRTUAL1R(uint64_t, _set_swapchain_create_info_and_get_next_pointer, GDExtensionPtr<void>);
+ GDVIRTUAL2R(uint64_t, _set_hand_joint_locations_and_get_next_pointer, int, GDExtensionPtr<void>);
+ GDVIRTUAL0R(uint64_t, _get_composition_layer);
virtual void on_register_metadata() override;
virtual void on_before_instance_created() override;
diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp
deleted file mode 100644
index 3da0ffd9c7..0000000000
--- a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-/**************************************************************************/
-/* openxr_fb_passthrough_extension_wrapper.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "openxr_fb_passthrough_extension_wrapper.h"
-
-#include "core/os/os.h"
-#include "scene/main/viewport.h"
-#include "scene/main/window.h"
-
-using namespace godot;
-
-OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::singleton = nullptr;
-
-OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::get_singleton() {
- return singleton;
-}
-
-OpenXRFbPassthroughExtensionWrapper::OpenXRFbPassthroughExtensionWrapper() {
- singleton = this;
-}
-
-OpenXRFbPassthroughExtensionWrapper::~OpenXRFbPassthroughExtensionWrapper() {
- cleanup();
-}
-
-HashMap<String, bool *> OpenXRFbPassthroughExtensionWrapper::get_requested_extensions() {
- HashMap<String, bool *> request_extensions;
-
- request_extensions[XR_FB_PASSTHROUGH_EXTENSION_NAME] = &fb_passthrough_ext;
- request_extensions[XR_FB_TRIANGLE_MESH_EXTENSION_NAME] = &fb_triangle_mesh_ext;
-
- return request_extensions;
-}
-
-void OpenXRFbPassthroughExtensionWrapper::cleanup() {
- fb_passthrough_ext = false;
- fb_triangle_mesh_ext = false;
-}
-
-Viewport *OpenXRFbPassthroughExtensionWrapper::get_main_viewport() {
- MainLoop *main_loop = OS::get_singleton()->get_main_loop();
- if (!main_loop) {
- print_error("Unable to retrieve main loop");
- return nullptr;
- }
-
- SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop);
- if (!scene_tree) {
- print_error("Unable to retrieve scene tree");
- return nullptr;
- }
-
- Viewport *viewport = scene_tree->get_root()->get_viewport();
- return viewport;
-}
-
-void OpenXRFbPassthroughExtensionWrapper::on_instance_created(const XrInstance instance) {
- if (fb_passthrough_ext) {
- bool result = initialize_fb_passthrough_extension(instance);
- if (!result) {
- print_error("Failed to initialize fb_passthrough extension");
- fb_passthrough_ext = false;
- }
- }
-
- if (fb_triangle_mesh_ext) {
- bool result = initialize_fb_triangle_mesh_extension(instance);
- if (!result) {
- print_error("Failed to initialize fb_triangle_mesh extension");
- fb_triangle_mesh_ext = false;
- }
- }
-
- if (fb_passthrough_ext) {
- OpenXRAPI::get_singleton()->register_composition_layer_provider(this);
- }
-}
-
-bool OpenXRFbPassthroughExtensionWrapper::is_passthrough_enabled() {
- return fb_passthrough_ext && passthrough_handle != XR_NULL_HANDLE && passthrough_layer != XR_NULL_HANDLE;
-}
-
-bool OpenXRFbPassthroughExtensionWrapper::start_passthrough() {
- if (passthrough_handle == XR_NULL_HANDLE) {
- return false;
- }
-
- if (is_passthrough_enabled()) {
- return true;
- }
-
- // Start the passthrough feature
- XrResult result = xrPassthroughStartFB(passthrough_handle);
- if (!is_valid_passthrough_result(result, "Failed to start passthrough")) {
- stop_passthrough();
- return false;
- }
-
- // Create the passthrough layer
- XrPassthroughLayerCreateInfoFB passthrough_layer_config = {
- XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB,
- nullptr,
- passthrough_handle,
- XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB,
- XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB,
- };
- result = xrCreatePassthroughLayerFB(OpenXRAPI::get_singleton()->get_session(), &passthrough_layer_config, &passthrough_layer);
- if (!is_valid_passthrough_result(result, "Failed to create the passthrough layer")) {
- stop_passthrough();
- return false;
- }
-
- // Check if the the viewport has transparent background
- Viewport *viewport = get_main_viewport();
- if (viewport && !viewport->has_transparent_background()) {
- print_error("Main viewport doesn't have transparent background! Passthrough may not properly render.");
- }
-
- return true;
-}
-
-void OpenXRFbPassthroughExtensionWrapper::on_session_created(const XrSession session) {
- if (fb_passthrough_ext) {
- // Create the passthrough feature and start it.
- XrPassthroughCreateInfoFB passthrough_create_info = {
- XR_TYPE_PASSTHROUGH_CREATE_INFO_FB,
- nullptr,
- 0,
- };
-
- XrResult result = xrCreatePassthroughFB(OpenXRAPI::get_singleton()->get_session(), &passthrough_create_info, &passthrough_handle);
- if (!OpenXRAPI::get_singleton()->xr_result(result, "Failed to create passthrough")) {
- passthrough_handle = XR_NULL_HANDLE;
- return;
- }
- }
-}
-
-XrCompositionLayerBaseHeader *OpenXRFbPassthroughExtensionWrapper::get_composition_layer() {
- if (is_passthrough_enabled()) {
- composition_passthrough_layer.layerHandle = passthrough_layer;
- return (XrCompositionLayerBaseHeader *)&composition_passthrough_layer;
- } else {
- return nullptr;
- }
-}
-
-void OpenXRFbPassthroughExtensionWrapper::stop_passthrough() {
- if (!fb_passthrough_ext) {
- return;
- }
-
- XrResult result;
- if (passthrough_layer != XR_NULL_HANDLE) {
- // Destroy the layer
- result = xrDestroyPassthroughLayerFB(passthrough_layer);
- OpenXRAPI::get_singleton()->xr_result(result, "Unable to destroy passthrough layer");
- passthrough_layer = XR_NULL_HANDLE;
- }
-
- if (passthrough_handle != XR_NULL_HANDLE) {
- result = xrPassthroughPauseFB(passthrough_handle);
- OpenXRAPI::get_singleton()->xr_result(result, "Unable to stop passthrough feature");
- }
-}
-
-void OpenXRFbPassthroughExtensionWrapper::on_session_destroyed() {
- if (fb_passthrough_ext) {
- stop_passthrough();
-
- XrResult result;
- if (passthrough_handle != XR_NULL_HANDLE) {
- result = xrDestroyPassthroughFB(passthrough_handle);
- OpenXRAPI::get_singleton()->xr_result(result, "Unable to destroy passthrough feature");
- passthrough_handle = XR_NULL_HANDLE;
- }
- }
-}
-
-void OpenXRFbPassthroughExtensionWrapper::on_instance_destroyed() {
- if (fb_passthrough_ext) {
- OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this);
- }
- cleanup();
-}
-
-bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_passthrough_extension(const XrInstance p_instance) {
- ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
-
- EXT_INIT_XR_FUNC_V(xrCreatePassthroughFB);
- EXT_INIT_XR_FUNC_V(xrDestroyPassthroughFB);
- EXT_INIT_XR_FUNC_V(xrPassthroughStartFB);
- EXT_INIT_XR_FUNC_V(xrPassthroughPauseFB);
- EXT_INIT_XR_FUNC_V(xrCreatePassthroughLayerFB);
- EXT_INIT_XR_FUNC_V(xrDestroyPassthroughLayerFB);
- EXT_INIT_XR_FUNC_V(xrPassthroughLayerPauseFB);
- EXT_INIT_XR_FUNC_V(xrPassthroughLayerResumeFB);
- EXT_INIT_XR_FUNC_V(xrPassthroughLayerSetStyleFB);
- EXT_INIT_XR_FUNC_V(xrCreateGeometryInstanceFB);
- EXT_INIT_XR_FUNC_V(xrDestroyGeometryInstanceFB);
- EXT_INIT_XR_FUNC_V(xrGeometryInstanceSetTransformFB);
-
- return true;
-}
-
-bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_triangle_mesh_extension(const XrInstance p_instance) {
- ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
-
- EXT_INIT_XR_FUNC_V(xrCreateTriangleMeshFB);
- EXT_INIT_XR_FUNC_V(xrDestroyTriangleMeshFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshGetVertexBufferFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshGetIndexBufferFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginUpdateFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshEndUpdateFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginVertexBufferUpdateFB);
- EXT_INIT_XR_FUNC_V(xrTriangleMeshEndVertexBufferUpdateFB);
-
- return true;
-}
diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h
deleted file mode 100644
index 045e424202..0000000000
--- a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h
+++ /dev/null
@@ -1,231 +0,0 @@
-/**************************************************************************/
-/* openxr_fb_passthrough_extension_wrapper.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H
-#define OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H
-
-#include "../openxr_api.h"
-#include "../util.h"
-#include "openxr_composition_layer_provider.h"
-#include "openxr_extension_wrapper.h"
-
-#include <map>
-
-class Viewport;
-
-// Wrapper for the set of Facebook XR passthrough extensions.
-class OpenXRFbPassthroughExtensionWrapper : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider {
-public:
- OpenXRFbPassthroughExtensionWrapper();
- ~OpenXRFbPassthroughExtensionWrapper();
-
- virtual HashMap<String, bool *> get_requested_extensions() override;
-
- void on_instance_created(const XrInstance instance) override;
-
- void on_session_created(const XrSession session) override;
-
- void on_session_destroyed() override;
-
- void on_instance_destroyed() override;
-
- XrCompositionLayerBaseHeader *get_composition_layer() override;
-
- bool is_passthrough_supported() {
- return fb_passthrough_ext;
- }
-
- bool is_passthrough_enabled();
-
- bool start_passthrough();
-
- void stop_passthrough();
-
- static OpenXRFbPassthroughExtensionWrapper *get_singleton();
-
-private:
- // Create a passthrough feature
- EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughFB,
- (XrSession), session,
- (const XrPassthroughCreateInfoFB *), create_info,
- (XrPassthroughFB *), feature_out)
-
- // Destroy a previously created passthrough feature
- EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughFB, (XrPassthroughFB), feature)
-
- //*** Passthrough feature state management functions *********
- // Start the passthrough feature
- EXT_PROTO_XRRESULT_FUNC1(xrPassthroughStartFB, (XrPassthroughFB), passthrough)
- // Pause the passthrough feature
- EXT_PROTO_XRRESULT_FUNC1(xrPassthroughPauseFB, (XrPassthroughFB), passthrough)
-
- EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughLayerFB, (XrSession), session,
- (const XrPassthroughLayerCreateInfoFB *), config,
- (XrPassthroughLayerFB *), layer_out)
-
- EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughLayerFB, (XrPassthroughLayerFB), layer)
-
- EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerPauseFB, (XrPassthroughLayerFB), layer)
- EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerResumeFB, (XrPassthroughLayerFB), layer)
-
- // Set the style of an existing passthrough layer. If the enabled feature set
- // doesn’t change, this is a lightweight operation that can be called in every
- // frame to animate the style. Changes that may incur a bigger cost:
- // - Enabling/disabling the color mapping, or changing the type of mapping
- // (monochromatic to RGBA or back).
- // - Changing `textureOpacityFactor` from 0 to non-zero or vice versa
- // - Changing `edgeColor[3]` from 0 to non-zero or vice versa
- // NOTE: For XR_FB_passthrough, all color values are treated as linear.
- EXT_PROTO_XRRESULT_FUNC2(xrPassthroughLayerSetStyleFB,
- (XrPassthroughLayerFB), layer,
- (const XrPassthroughStyleFB *), style)
-
- // Create a geometry instance to be used as a projection surface for passthrough.
- // A geometry instance assigns a triangle mesh as part of the specified layer's
- // projection surface.
- // The operation is only valid if the passthrough layer's purpose has been set to
- // `XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB`. Otherwise, the call this function will
- // result in an error. In the specified layer, Passthrough will be visible where the view
- // is covered by the user-specified geometries.
- //
- // A triangle mesh object can be instantiated multiple times - in the same or different layers'
- // projection surface. Each instantiation has its own transformation, which
- // can be updated using `xrGeometryInstanceSetTransformFB`.
- EXT_PROTO_XRRESULT_FUNC3(xrCreateGeometryInstanceFB,
- (XrSession), session,
- (const XrGeometryInstanceCreateInfoFB *), create_info,
- (XrGeometryInstanceFB *), out_geometry_instance)
-
- // Destroys a previously created geometry instance from passthrough rendering.
- // This removes the geometry instance from passthrough rendering.
- // The operation has no effect on other instances or the underlying mesh.
- EXT_PROTO_XRRESULT_FUNC1(xrDestroyGeometryInstanceFB, (XrGeometryInstanceFB), instance)
-
- // Update the transformation of a passthrough geometry instance.
- EXT_PROTO_XRRESULT_FUNC2(xrGeometryInstanceSetTransformFB,
- (XrGeometryInstanceFB), instance,
- (const XrGeometryInstanceTransformFB *), transformation)
-
- // Create a triangle mesh geometry object.
- // Depending on the behavior flags, the mesh could be created immutable (data is assigned
- // at creation and cannot be changed) or mutable (the mesh is created empty and can be updated
- // by calling begin/end update functions).
- EXT_PROTO_XRRESULT_FUNC3(xrCreateTriangleMeshFB,
- (XrSession), session,
- (const XrTriangleMeshCreateInfoFB *), create_info,
- (XrTriangleMeshFB *), out_triangle_mesh)
-
- // Destroy an `XrTriangleMeshFB` object along with its data. The mesh buffers must not be
- // accessed anymore after their parent mesh object has been destroyed.
- EXT_PROTO_XRRESULT_FUNC1(xrDestroyTriangleMeshFB, (XrTriangleMeshFB), mesh)
-
- // Retrieve a pointer to the vertex buffer. The vertex buffer is structured as an array of 3 floats
- // per vertex representing x, y, and z: `[x0, y0, z0, x1, y1, z1, ...]`. The size of the buffer is
- // `maxVertexCount * 3` floats. The application must call `xrTriangleMeshBeginUpdateFB` or
- // `xrTriangleMeshBeginVertexBufferUpdateFB` before making modifications to the vertex
- // buffer. The buffer location is guaranteed to remain constant over the lifecycle of the mesh
- // object.
- EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetVertexBufferFB,
- (XrTriangleMeshFB), mesh,
- (XrVector3f **), out_vertex_buffer)
-
- // Retrieve the index buffer that defines the topology of the triangle mesh. Each triplet of
- // consecutive elements point to three vertices in the vertex buffer and thus form a triangle. The
- // size of each element is `indexElementSize` bytes, and thus the size of the buffer is
- // `maxTriangleCount * 3 * indexElementSize` bytes. The application must call
- // `xrTriangleMeshBeginUpdateFB` before making modifications to the index buffer. The buffer
- // location is guaranteed to remain constant over the lifecycle of the mesh object.
- EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetIndexBufferFB,
- (XrTriangleMeshFB), mesh,
- (uint32_t **), out_index_buffer)
-
- // Begin updating the mesh buffer data. The application must call this function before it makes any
- // modifications to the buffers retrieved by `xrTriangleMeshGetVertexBufferFB` and
- // `xrTriangleMeshGetIndexBufferFB`. If only the vertex buffer needs to be updated,
- // `xrTriangleMeshBeginVertexBufferUpdateFB` can be used instead. To commit the
- // modifications, the application must call `xrTriangleMeshEndUpdateFB`.
- EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshBeginUpdateFB, (XrTriangleMeshFB), mesh)
-
- // Signal the API that the application has finished updating the mesh buffers after a call to
- // `xrTriangleMeshBeginUpdateFB`. `vertexCount` and `triangleCount` specify the actual
- // number of primitives that make up the mesh after the update. They must be larger than zero but
- // smaller or equal to the maximum counts defined at create time. Buffer data beyond these counts
- // is ignored.
- EXT_PROTO_XRRESULT_FUNC3(xrTriangleMeshEndUpdateFB,
- (XrTriangleMeshFB), mesh,
- (uint32_t), vertexCount,
- (uint32_t), triangle_count)
-
- // Update the vertex positions of a triangle mesh. Can only be called once the mesh topology has
- // been set using `xrTriangleMeshBeginUpdateFB`/`xrTriangleMeshEndUpdateFB`. The
- // vertex count is defined by the last invocation to `xrTriangleMeshEndUpdateFB`. Once the
- // modification is done, `xrTriangleMeshEndVertexBufferUpdateFB` must be called.
- EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshBeginVertexBufferUpdateFB,
- (XrTriangleMeshFB), mesh,
- (uint32_t *), out_vertex_count)
-
- // Signal the API that the contents of the vertex buffer data has been updated
- // after a call to `xrTriangleMeshBeginVertexBufferUpdateFB`.
- EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshEndVertexBufferUpdateFB, (XrTriangleMeshFB), mesh)
-
- bool initialize_fb_passthrough_extension(const XrInstance instance);
-
- bool initialize_fb_triangle_mesh_extension(const XrInstance instance);
-
- void cleanup();
-
- // TODO: Temporary workaround (https://github.com/GodotVR/godot_openxr/issues/138)
- // Address a bug in the passthrough api where XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB is
- // returned even when the operation is valid on Meta Quest devices.
- // The issue should be addressed on that platform in OS release v37.
- inline bool is_valid_passthrough_result(XrResult result, const char *format) {
- return OpenXRAPI::get_singleton()->xr_result(result, format) || result == XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB;
- }
-
- Viewport *get_main_viewport();
-
- static OpenXRFbPassthroughExtensionWrapper *singleton;
-
- bool fb_passthrough_ext = false; // required for any passthrough functionality
- bool fb_triangle_mesh_ext = false; // only use for projected passthrough
-
- XrPassthroughFB passthrough_handle = XR_NULL_HANDLE;
- XrPassthroughLayerFB passthrough_layer = XR_NULL_HANDLE;
-
- XrCompositionLayerPassthroughFB composition_passthrough_layer = {
- XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB,
- nullptr,
- XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT,
- XR_NULL_HANDLE,
- XR_NULL_HANDLE,
- };
-};
-
-#endif // OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H
diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
index 1289183ea4..c3f692185b 100644
--- a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
+++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_fb_update_swapchain_extension.h"
-// always include this as late as possible
+// Always include this as late as possible.
#include "../openxr_platform_inc.h"
OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr;
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index 0d667b56c6..884fb41a3c 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -60,6 +60,7 @@ HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions()
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
+ request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
return request_extensions;
}
@@ -137,20 +138,32 @@ void OpenXRHandTrackingExtension::on_process() {
for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) {
- XrHandTrackerCreateInfoEXT createInfo = {
+ void *next_pointer = nullptr;
+
+ // Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking.
+ // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided
+ // on runtimes that support this.
+ XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT };
+ XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources };
+ if (hand_tracking_source_ext) {
+ // If supported include this info
+ next_pointer = &data_source_info;
+ }
+
+ XrHandTrackerCreateInfoEXT create_info = {
XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type
- nullptr, // next
+ next_pointer, // next
i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, // hand
XR_HAND_JOINT_SET_DEFAULT_EXT, // handJointSet
};
- result = xrCreateHandTrackerEXT(OpenXRAPI::get_singleton()->get_session(), &createInfo, &hand_trackers[i].hand_tracker);
+ result = xrCreateHandTrackerEXT(OpenXRAPI::get_singleton()->get_session(), &create_info, &hand_trackers[i].hand_tracker);
if (XR_FAILED(result)) {
// not successful? then we do nothing.
print_line("OpenXR: Failed to obtain hand tracking information [", OpenXRAPI::get_singleton()->get_error_string(result), "]");
hand_trackers[i].is_initialized = false;
} else {
- void *next_pointer = nullptr;
+ next_pointer = nullptr;
hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
hand_trackers[i].velocities.next = next_pointer;
@@ -158,6 +171,22 @@ void OpenXRHandTrackingExtension::on_process() {
hand_trackers[i].velocities.jointVelocities = hand_trackers[i].joint_velocities;
next_pointer = &hand_trackers[i].velocities;
+ if (hand_tracking_source_ext) {
+ hand_trackers[i].data_source.type = XR_TYPE_HAND_TRACKING_DATA_SOURCE_STATE_EXT;
+ hand_trackers[i].data_source.next = next_pointer;
+ hand_trackers[i].data_source.isActive = false;
+ hand_trackers[i].data_source.dataSource = XrHandTrackingDataSourceEXT(0);
+ next_pointer = &hand_trackers[i].data_source;
+ }
+
+ // Needed for vendor hand tracking extensions implemented from GDExtension.
+ for (OpenXRExtensionWrapper *wrapper : OpenXRAPI::get_singleton()->get_registered_extension_wrappers()) {
+ void *np = wrapper->set_hand_joint_locations_and_get_next_pointer(i, next_pointer);
+ if (np != nullptr) {
+ next_pointer = np;
+ }
+ }
+
hand_trackers[i].locations.type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
hand_trackers[i].locations.next = next_pointer;
hand_trackers[i].locations.isActive = false;
@@ -171,14 +200,9 @@ void OpenXRHandTrackingExtension::on_process() {
if (hand_trackers[i].is_initialized) {
void *next_pointer = nullptr;
- XrHandJointsMotionRangeInfoEXT motionRangeInfo;
-
+ XrHandJointsMotionRangeInfoEXT motion_range_info = { XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, next_pointer, hand_trackers[i].motion_range };
if (hand_motion_range_ext) {
- motionRangeInfo.type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT;
- motionRangeInfo.next = next_pointer;
- motionRangeInfo.handJointsMotionRange = hand_trackers[i].motion_range;
-
- next_pointer = &motionRangeInfo;
+ next_pointer = &motion_range_info;
}
XrHandJointsLocateInfoEXT locateInfo = {
@@ -240,6 +264,25 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra
return hand_trackers[p_hand].motion_range;
}
+OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN);
+
+ if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) {
+ switch (hand_trackers[p_hand].data_source.dataSource) {
+ case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT:
+ return OPENXR_SOURCE_UNOBSTRUCTED;
+
+ case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT:
+ return OPENXR_SOURCE_CONTROLLER;
+
+ default:
+ return OPENXR_SOURCE_UNKNOWN;
+ }
+ }
+
+ return OPENXR_SOURCE_UNKNOWN;
+}
+
void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range) {
ERR_FAIL_UNSIGNED_INDEX(p_hand, OPENXR_MAX_TRACKED_HANDS);
hand_trackers[p_hand].motion_range = p_motion_range;
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index f9b26fd604..967538b377 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -43,9 +43,17 @@ public:
OPENXR_MAX_TRACKED_HANDS
};
+ enum HandTrackedSource {
+ OPENXR_SOURCE_UNKNOWN,
+ OPENXR_SOURCE_UNOBSTRUCTED,
+ OPENXR_SOURCE_CONTROLLER,
+ OPENXR_SOURCE_MAX
+ };
+
struct HandTracker {
bool is_initialized = false;
XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
+ HandTrackedSource source = OPENXR_SOURCE_UNKNOWN;
XrHandTrackerEXT hand_tracker = XR_NULL_HANDLE;
XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT];
@@ -53,6 +61,7 @@ public:
XrHandJointVelocitiesEXT velocities;
XrHandJointLocationsEXT locations;
+ XrHandTrackingDataSourceStateEXT data_source;
};
static OpenXRHandTrackingExtension *get_singleton();
@@ -77,6 +86,8 @@ public:
XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const;
void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range);
+ HandTrackedSource get_hand_tracking_source(HandTrackedHands p_hand) const;
+
XrSpaceLocationFlags get_hand_joint_location_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
@@ -96,6 +107,7 @@ private:
// related extensions
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
+ bool hand_tracking_source_ext = false;
// functions
void cleanup_hand_tracking();
diff --git a/modules/openxr/extensions/openxr_local_floor_extension.cpp b/modules/openxr/extensions/openxr_local_floor_extension.cpp
new file mode 100644
index 0000000000..8e06dd8ed5
--- /dev/null
+++ b/modules/openxr/extensions/openxr_local_floor_extension.cpp
@@ -0,0 +1,59 @@
+/**************************************************************************/
+/* openxr_local_floor_extension.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_local_floor_extension.h"
+
+#include "core/string/print_string.h"
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::singleton = nullptr;
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRLocalFloorExtension::OpenXRLocalFloorExtension() {
+ singleton = this;
+}
+
+OpenXRLocalFloorExtension::~OpenXRLocalFloorExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRLocalFloorExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_EXT_LOCAL_FLOOR_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+bool OpenXRLocalFloorExtension::is_available() {
+ return available;
+}
diff --git a/modules/openxr/extensions/openxr_local_floor_extension.h b/modules/openxr/extensions/openxr_local_floor_extension.h
new file mode 100644
index 0000000000..dff97d9954
--- /dev/null
+++ b/modules/openxr/extensions/openxr_local_floor_extension.h
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* openxr_local_floor_extension.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_LOCAL_FLOOR_EXTENSION_H
+#define OPENXR_LOCAL_FLOOR_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRLocalFloorExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRLocalFloorExtension *get_singleton();
+
+ OpenXRLocalFloorExtension();
+ virtual ~OpenXRLocalFloorExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+private:
+ static OpenXRLocalFloorExtension *singleton;
+
+ bool available = false;
+};
+
+#endif // OPENXR_LOCAL_FLOOR_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_meta_controller_extension.cpp b/modules/openxr/extensions/openxr_meta_controller_extension.cpp
new file mode 100644
index 0000000000..72dc74bf64
--- /dev/null
+++ b/modules/openxr/extensions/openxr_meta_controller_extension.cpp
@@ -0,0 +1,184 @@
+/**************************************************************************/
+/* openxr_meta_controller_extension.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_meta_controller_extension.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+
+HashMap<String, bool *> OpenXRMetaControllerExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_FB_TOUCH_CONTROLLER_PROXIMITY_EXTENSION_NAME] = &available[META_TOUCH_PROXIMITY];
+ request_extensions[XR_FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME] = &available[META_TOUCH_PRO];
+ request_extensions[XR_META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME] = &available[META_TOUCH_PLUS];
+
+ return request_extensions;
+}
+
+bool OpenXRMetaControllerExtension::is_available(MetaControllers p_type) {
+ return available[p_type];
+}
+
+void OpenXRMetaControllerExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Note, we register controllers regardless if they are supported on the current hardware.
+
+ // Normal touch controller is part of the core spec, but we do have some extensions.
+ metadata->register_io_path("/interaction_profiles/oculus/touch_controller", "Trigger proximity", "/user/hand/left", "/user/hand/left/input/trigger/proximity_fb ", "XR_FB_touch_controller_proximity", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/oculus/touch_controller", "Trigger proximity", "/user/hand/right", "/user/hand/right/input/trigger/proximity_fb ", "XR_FB_touch_controller_proximity", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/oculus/touch_controller", "Thumb proximity", "/user/hand/left", "/user/hand/left/input/thumb_fb/proximity_fb ", "XR_FB_touch_controller_proximity", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/oculus/touch_controller", "Thumb proximity", "/user/hand/right", "/user/hand/right/input/thumb_fb/proximity_fb ", "XR_FB_touch_controller_proximity", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ // Touch controller pro (Quest Pro)
+ metadata->register_interaction_profile("Touch controller pro", "/interaction_profiles/facebook/touch_controller_pro", "XR_FB_touch_controller_pro");
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Menu click", "/user/hand/left", "/user/hand/left/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger proximity", "/user/hand/left", "/user/hand/left/input/trigger/proximity_fb", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger curl", "/user/hand/left", "/user/hand/left/input/trigger/curl_fb", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger slide", "/user/hand/left", "/user/hand/left/input/trigger/slide_fb", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger force", "/user/hand/left", "/user/hand/left/input/trigger/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger proximity", "/user/hand/right", "/user/hand/right/input/trigger/proximity_fb", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger curl", "/user/hand/right", "/user/hand/right/input/trigger/curl_fb", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger slide", "/user/hand/right", "/user/hand/right/input/trigger/slide_fb", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Trigger force", "/user/hand/right", "/user/hand/right/input/trigger/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumb proximity", "/user/hand/left", "/user/hand/left/input/thumb_fb/proximity_fb", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumb proximity", "/user/hand/right", "/user/hand/right/input/thumb_fb/proximity_fb", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick X", "/user/hand/left", "/user/hand/left/input/thumbstick/x", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick Y", "/user/hand/left", "/user/hand/left/input/thumbstick/y", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick X", "/user/hand/right", "/user/hand/right/input/thumbstick/x", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick Y", "/user/hand/right", "/user/hand/right/input/thumbstick/y", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbrest touch", "/user/hand/left", "/user/hand/left/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbrest force", "/user/hand/left", "/user/hand/left/input/thumbrest/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbrest touch", "/user/hand/right", "/user/hand/right/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Thumbrest force", "/user/hand/right", "/user/hand/right/input/thumbrest/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Stylus force", "/user/hand/left", "/user/hand/left/input/stylus_fb/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Stylus force", "/user/hand/right", "/user/hand/right/input/stylus_fb/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic trigger output", "/user/hand/left", "/user/hand/left/output/haptic_trigger_fb", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic thumb output", "/user/hand/left", "/user/hand/left/output/haptic_thumb_fb", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic trigger output", "/user/hand/right", "/user/hand/right/output/haptic_trigger_fb", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/facebook/touch_controller_pro", "Haptic thumb output", "/user/hand/right", "/user/hand/right/output/haptic_thumb_fb", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+
+ // Touch controller plus (Quest 3)
+ metadata->register_interaction_profile("Touch controller plus", "/interaction_profiles/meta/touch_controller_plus", "XR_META_touch_controller_plus");
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Menu click", "/user/hand/left", "/user/hand/left/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger proximity", "/user/hand/left", "/user/hand/left/input/trigger/proximity_meta", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger curl", "/user/hand/left", "/user/hand/left/input/trigger/curl_meta", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger slide", "/user/hand/left", "/user/hand/left/input/trigger/slide_meta", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger force", "/user/hand/left", "/user/hand/left/input/trigger/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger proximity", "/user/hand/right", "/user/hand/right/input/trigger/proximity_meta", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger curl", "/user/hand/right", "/user/hand/right/input/trigger/curl_meta", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger slide", "/user/hand/right", "/user/hand/right/input/trigger/slide_meta", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Trigger force", "/user/hand/right", "/user/hand/right/input/trigger/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumb proximity", "/user/hand/left", "/user/hand/left/input/thumb_meta/proximity_meta", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumb proximity", "/user/hand/right", "/user/hand/right/input/thumb_meta/proximity_meta", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick X", "/user/hand/left", "/user/hand/left/input/thumbstick/x", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick Y", "/user/hand/left", "/user/hand/left/input/thumbstick/y", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick X", "/user/hand/right", "/user/hand/right/input/thumbstick/x", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick Y", "/user/hand/right", "/user/hand/right/input/thumbstick/y", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbrest touch", "/user/hand/left", "/user/hand/left/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Thumbrest touch", "/user/hand/right", "/user/hand/right/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/meta/touch_controller_plus", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+}
diff --git a/modules/openxr/extensions/openxr_meta_controller_extension.h b/modules/openxr/extensions/openxr_meta_controller_extension.h
new file mode 100644
index 0000000000..7a60d2a0b9
--- /dev/null
+++ b/modules/openxr/extensions/openxr_meta_controller_extension.h
@@ -0,0 +1,55 @@
+/**************************************************************************/
+/* openxr_meta_controller_extension.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_META_CONTROLLER_EXTENSION_H
+#define OPENXR_META_CONTROLLER_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRMetaControllerExtension : public OpenXRExtensionWrapper {
+public:
+ enum MetaControllers {
+ META_TOUCH_PROXIMITY, // Proximity extensions for normal touch controllers
+ META_TOUCH_PRO, // Touch controller for the Quest Pro
+ META_TOUCH_PLUS, // Touch controller for the Quest Plus
+ META_MAX_CONTROLLERS
+ };
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available(MetaControllers p_type);
+
+ virtual void on_register_metadata() override;
+
+private:
+ bool available[META_MAX_CONTROLLERS] = { false, false, false };
+};
+
+#endif // OPENXR_META_CONTROLLER_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp
index c6082ca404..de542828c3 100644
--- a/modules/openxr/extensions/openxr_android_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_android_extension.h"
-#include "../openxr_api.h"
+#include "../../openxr_api.h"
#include "java_godot_wrapper.h"
#include "os_android.h"
diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h
index 0e7c44d6d5..e51b5824e8 100644
--- a/modules/openxr/extensions/openxr_android_extension.h
+++ b/modules/openxr/extensions/platform/openxr_android_extension.h
@@ -31,8 +31,8 @@
#ifndef OPENXR_ANDROID_EXTENSION_H
#define OPENXR_ANDROID_EXTENSION_H
-#include "../util.h"
-#include "openxr_extension_wrapper.h"
+#include "../../util.h"
+#include "../openxr_extension_wrapper.h"
class OpenXRAndroidExtension : public OpenXRExtensionWrapper {
public:
diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
index 9038e9f458..d92084a220 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
@@ -32,7 +32,7 @@
#ifdef GLES3_ENABLED
-#include "../openxr_util.h"
+#include "../../openxr_util.h"
#include "drivers/gles3/effects/copy_effects.h"
#include "drivers/gles3/storage/texture_storage.h"
@@ -131,9 +131,9 @@ bool OpenXROpenGLExtension::check_graphics_api_support(XrVersion p_desired_versi
#ifdef WIN32
XrGraphicsBindingOpenGLWin32KHR OpenXROpenGLExtension::graphics_binding_gl;
-#elif ANDROID_ENABLED
+#elif defined(ANDROID_ENABLED)
XrGraphicsBindingOpenGLESAndroidKHR OpenXROpenGLExtension::graphics_binding_gl;
-#else
+#elif defined(X11_ENABLED)
XrGraphicsBindingOpenGLXlibKHR OpenXROpenGLExtension::graphics_binding_gl;
#endif
@@ -147,20 +147,24 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
DisplayServer *display_server = DisplayServer::get_singleton();
+#ifdef WAYLAND_ENABLED
+ ERR_FAIL_COND_V_MSG(display_server->get_name() == "Wayland", p_next_pointer, "OpenXR is not yet supported on OpenGL Wayland.");
+#endif
+
#ifdef WIN32
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR,
graphics_binding_gl.next = p_next_pointer;
graphics_binding_gl.hDC = (HDC)display_server->window_get_native_handle(DisplayServer::WINDOW_VIEW);
graphics_binding_gl.hGLRC = (HGLRC)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
-#elif ANDROID_ENABLED
+#elif defined(ANDROID_ENABLED)
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR;
graphics_binding_gl.next = p_next_pointer;
graphics_binding_gl.display = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE);
graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122
graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
-#else
+#elif defined(X11_ENABLED)
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
graphics_binding_gl.next = p_next_pointer;
diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h
index 3b0aa0bce9..a3052d3f53 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h
@@ -33,14 +33,14 @@
#ifdef GLES3_ENABLED
-#include "../openxr_api.h"
-#include "../util.h"
-#include "openxr_extension_wrapper.h"
+#include "../../openxr_api.h"
+#include "../../util.h"
+#include "../openxr_extension_wrapper.h"
#include "core/templates/vector.h"
-// always include this as late as possible
-#include "../openxr_platform_inc.h"
+// Always include this as late as possible.
+#include "../../openxr_platform_inc.h"
class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper {
public:
@@ -65,9 +65,9 @@ private:
#ifdef WIN32
static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl;
-#elif ANDROID_ENABLED
+#elif defined(ANDROID_ENABLED)
static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl;
-#else
+#else // Linux/X11
static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl;
#endif
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp
index 9429d9e082..f5e7fc192c 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_vulkan_extension.h"
-#include "../openxr_util.h"
+#include "../../openxr_util.h"
#include "core/string/print_string.h"
#include "servers/rendering/renderer_rd/effects/copy_effects.h"
@@ -38,14 +38,6 @@
#include "servers/rendering/rendering_server_globals.h"
#include "servers/rendering_server.h"
-OpenXRVulkanExtension::OpenXRVulkanExtension() {
- VulkanContext::set_vulkan_hooks(this);
-}
-
-OpenXRVulkanExtension::~OpenXRVulkanExtension() {
- VulkanContext::set_vulkan_hooks(nullptr);
-}
-
HashMap<String, bool *> OpenXRVulkanExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -178,10 +170,6 @@ bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) {
bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) {
ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
- // the first entry in our queue list should be the one we need to remember...
- vulkan_queue_family_index = p_device_create_info->pQueueCreateInfos[0].queueFamilyIndex;
- vulkan_queue_index = 0; // ??
-
XrVulkanDeviceCreateInfoKHR create_info = {
XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR, // type
nullptr, // next
@@ -209,9 +197,17 @@ bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_dev
return true;
}
+void OpenXRVulkanExtension::set_direct_queue_family_and_index(uint32_t p_queue_family_index, uint32_t p_queue_index) {
+ vulkan_queue_family_index = p_queue_family_index;
+ vulkan_queue_index = p_queue_index;
+}
+
XrGraphicsBindingVulkanKHR OpenXRVulkanExtension::graphics_binding_vulkan;
void *OpenXRVulkanExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) {
+ DEV_ASSERT(vulkan_queue_family_index < UINT32_MAX && "Direct queue family index was not specified yet.");
+ DEV_ASSERT(vulkan_queue_index < UINT32_MAX && "Direct queue index was not specified yet.");
+
graphics_binding_vulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
graphics_binding_vulkan.next = p_next_pointer;
graphics_binding_vulkan.instance = vulkan_instance;
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
index f31621fda0..a3f86a9968 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
@@ -31,28 +31,30 @@
#ifndef OPENXR_VULKAN_EXTENSION_H
#define OPENXR_VULKAN_EXTENSION_H
-#include "../openxr_api.h"
-#include "../util.h"
-#include "openxr_extension_wrapper.h"
+#include "../../openxr_api.h"
+#include "../../util.h"
+#include "../openxr_extension_wrapper.h"
#include "core/templates/vector.h"
+#include "drivers/vulkan/vulkan_hooks.h"
-// always include this as late as possible
-#include "../openxr_platform_inc.h"
+// Always include this as late as possible.
+#include "../../openxr_platform_inc.h"
class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks {
public:
- OpenXRVulkanExtension();
- virtual ~OpenXRVulkanExtension() override;
+ OpenXRVulkanExtension() = default;
+ virtual ~OpenXRVulkanExtension() override = default;
virtual HashMap<String, bool *> get_requested_extensions() override;
virtual void on_instance_created(const XrInstance p_instance) override;
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
- virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override;
- virtual bool get_physical_device(VkPhysicalDevice *r_device) override;
- virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override;
+ virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override final;
+ virtual bool get_physical_device(VkPhysicalDevice *r_device) override final;
+ virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override final;
+ virtual void set_direct_queue_family_and_index(uint32_t p_queue_family_index, uint32_t p_queue_index) override final;
virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override;
virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override;
@@ -76,8 +78,8 @@ private:
VkInstance vulkan_instance = nullptr;
VkPhysicalDevice vulkan_physical_device = nullptr;
VkDevice vulkan_device = nullptr;
- uint32_t vulkan_queue_family_index = 0;
- uint32_t vulkan_queue_index = 0;
+ uint32_t vulkan_queue_family_index = UINT32_MAX;
+ uint32_t vulkan_queue_index = UINT32_MAX;
EXT_PROTO_XRRESULT_FUNC3(xrGetVulkanGraphicsRequirements2KHR, (XrInstance), p_instance, (XrSystemId), p_system_id, (XrGraphicsRequirementsVulkanKHR *), p_graphics_requirements)
EXT_PROTO_XRRESULT_FUNC4(xrCreateVulkanInstanceKHR, (XrInstance), p_instance, (const XrVulkanInstanceCreateInfoKHR *), p_create_info, (VkInstance *), r_vulkan_instance, (VkResult *), r_vulkan_result)
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index da6fd2e9b2..36c3faaf75 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -46,17 +46,16 @@
#include "openxr_platform_inc.h"
#ifdef VULKAN_ENABLED
-#include "extensions/openxr_vulkan_extension.h"
+#include "extensions/platform/openxr_vulkan_extension.h"
#endif
#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
-#include "extensions/openxr_opengl_extension.h"
+#include "extensions/platform/openxr_opengl_extension.h"
#endif
#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
#include "extensions/openxr_fb_foveation_extension.h"
-#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_fb_update_swapchain_extension.h"
#ifdef ANDROID_ENABLED
@@ -665,13 +664,6 @@ bool OpenXRAPI::load_supported_reference_spaces() {
print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
}
- // Check value we loaded at startup...
- if (!is_reference_space_supported(reference_space)) {
- print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0]));
-
- reference_space = supported_reference_spaces[0];
- }
-
return true;
}
@@ -687,58 +679,161 @@ bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_sp
return false;
}
-bool OpenXRAPI::setup_spaces() {
- XrResult result;
+bool OpenXRAPI::setup_play_space() {
+ ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
XrPosef identityPose = {
{ 0.0, 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 0.0 }
};
+ XrReferenceSpaceType new_reference_space;
+ XrSpace new_play_space = XR_NULL_HANDLE;
+ bool will_emulate_local_floor = false;
+
+ if (is_reference_space_supported(requested_reference_space)) {
+ new_reference_space = requested_reference_space;
+ } else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) {
+ print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces.");
+
+ new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ will_emulate_local_floor = true;
+ } else {
+ // Fallback on LOCAL, which all OpenXR runtimes are required to support.
+ print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space."));
+ new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ }
+
+ XrReferenceSpaceCreateInfo play_space_create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ new_reference_space, // referenceSpaceType
+ identityPose, // poseInReferenceSpace
+ };
+
+ XrResult result = xrCreateReferenceSpace(session, &play_space_create_info, &new_play_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create play space [", get_error_string(result), "]");
+ return false;
+ }
+
+ // If we've previously created a play space, clean it up first.
+ if (play_space != XR_NULL_HANDLE) {
+ xrDestroySpace(play_space);
+ }
+ play_space = new_play_space;
+ reference_space = new_reference_space;
+
+ emulating_local_floor = will_emulate_local_floor;
+ if (emulating_local_floor) {
+ // We'll use the STAGE space to get the floor height, but we can't do that until
+ // after xrWaitFrame(), so just set this flag for now.
+ should_reset_emulated_floor_height = true;
+ }
+
+ return true;
+}
+
+bool OpenXRAPI::setup_view_space() {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
- // create play space
- {
- if (!is_reference_space_supported(reference_space)) {
- print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported.");
- return false;
- }
+ if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) {
+ print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported.");
+ return false;
+ }
- XrReferenceSpaceCreateInfo play_space_create_info = {
- XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
- nullptr, // next
- reference_space, // referenceSpaceType
- identityPose // poseInReferenceSpace
- };
+ XrPosef identityPose = {
+ { 0.0, 0.0, 0.0, 1.0 },
+ { 0.0, 0.0, 0.0 }
+ };
- result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
- if (XR_FAILED(result)) {
- print_line("OpenXR: Failed to create play space [", get_error_string(result), "]");
- return false;
- }
+ XrReferenceSpaceCreateInfo view_space_create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType
+ identityPose // poseInReferenceSpace
+ };
+
+ XrResult result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create view space [", get_error_string(result), "]");
+ return false;
}
- // create view space
- {
- if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) {
- print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported.");
- return false;
- }
+ return true;
+}
- XrReferenceSpaceCreateInfo view_space_create_info = {
- XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
- nullptr, // next
- XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType
- identityPose // poseInReferenceSpace
- };
+bool OpenXRAPI::reset_emulated_floor_height() {
+ ERR_FAIL_COND_V(!emulating_local_floor, false);
- result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space);
- if (XR_FAILED(result)) {
- print_line("OpenXR: Failed to create view space [", get_error_string(result), "]");
- return false;
- }
+ // This is based on the example code in the OpenXR spec which shows how to
+ // emulate LOCAL_FLOOR if it's not supported.
+ // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor
+
+ XrResult result;
+
+ XrPosef identityPose = {
+ { 0.0, 0.0, 0.0, 1.0 },
+ { 0.0, 0.0, 0.0 }
+ };
+
+ XrSpace local_space = XR_NULL_HANDLE;
+ XrSpace stage_space = XR_NULL_HANDLE;
+
+ XrReferenceSpaceCreateInfo create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
+ identityPose, // poseInReferenceSpace
+ };
+
+ result = xrCreateReferenceSpace(session, &create_info, &local_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ return false;
+ }
+
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
+ result = xrCreateReferenceSpace(session, &create_info, &stage_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ xrDestroySpace(local_space);
+ return false;
+ }
+
+ XrSpaceLocation stage_location = {
+ XR_TYPE_SPACE_LOCATION, // type
+ nullptr, // next
+ 0, // locationFlags
+ identityPose, // pose
+ };
+
+ result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location);
+
+ xrDestroySpace(local_space);
+ xrDestroySpace(stage_space);
+
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ return false;
}
+ XrSpace new_play_space;
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y;
+ result = xrCreateReferenceSpace(session, &create_info, &new_play_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]");
+ return false;
+ }
+
+ xrDestroySpace(play_space);
+ play_space = new_play_space;
+
+ // If we've made it this far, it means we can properly emulate LOCAL_FLOOR, so we'll
+ // report that as the reference space to the outside world.
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+
return true;
}
@@ -1180,10 +1275,14 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
view_configuration = p_view_configuration;
}
-void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) {
- ERR_FAIL_COND(is_initialized());
+bool OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
+ requested_reference_space = p_requested_reference_space;
- reference_space = p_reference_space;
+ if (is_initialized()) {
+ return setup_play_space();
+ }
+
+ return true;
}
void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
@@ -1384,7 +1483,12 @@ bool OpenXRAPI::initialize_session() {
return false;
}
- if (!setup_spaces()) {
+ if (!setup_play_space()) {
+ destroy_session();
+ return false;
+ }
+
+ if (!setup_view_space()) {
destroy_session();
return false;
}
@@ -1415,6 +1519,10 @@ void OpenXRAPI::unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension
registered_extension_wrappers.erase(p_extension_wrapper);
}
+const Vector<OpenXRExtensionWrapper *> &OpenXRAPI::get_registered_extension_wrappers() {
+ return registered_extension_wrappers;
+}
+
void OpenXRAPI::register_extension_metadata() {
for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) {
extension_wrapper->on_register_metadata();
@@ -1628,6 +1736,9 @@ bool OpenXRAPI::poll_events() {
XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
+ if (emulating_local_floor) {
+ should_reset_emulated_floor_height = true;
+ }
if (event->poseValid && xr_interface) {
xr_interface->on_pose_recentered();
}
@@ -1783,6 +1894,11 @@ void OpenXRAPI::pre_render() {
frame_state.predictedDisplayPeriod = 0;
}
+ if (unlikely(should_reset_emulated_floor_height)) {
+ reset_emulated_floor_height();
+ should_reset_emulated_floor_height = false;
+ }
+
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_pre_render();
}
@@ -2136,10 +2252,13 @@ OpenXRAPI::OpenXRAPI() {
int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space");
switch (reference_space_setting) {
case 0: {
- reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
} break;
case 1: {
- reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ } break;
+ case 2: {
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
} break;
default:
break;
@@ -2738,7 +2857,7 @@ bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) {
}
}
- ERR_FAIL_COND_V(active_sets.size() == 0, false);
+ ERR_FAIL_COND_V(active_sets.is_empty(), false);
XrActionsSyncInfo sync_info = {
XR_TYPE_ACTIONS_SYNC_INFO, // type
@@ -3002,11 +3121,30 @@ bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_ble
}
bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) {
+ if (emulate_environment_blend_mode_alpha_blend && p_blend_mode == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND) {
+ requested_environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
+ environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ return true;
+ }
// We allow setting this when not initialized and will check if it is supported when initializing.
// After OpenXR is initialized we verify we're setting a supported blend mode.
- if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) {
+ else if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) {
+ requested_environment_blend_mode = p_blend_mode;
environment_blend_mode = p_blend_mode;
return true;
}
return false;
}
+
+void OpenXRAPI::set_emulate_environment_blend_mode_alpha_blend(bool p_enabled) {
+ emulate_environment_blend_mode_alpha_blend = p_enabled;
+}
+
+OpenXRAPI::OpenXRAlphaBlendModeSupport OpenXRAPI::is_environment_blend_mode_alpha_blend_supported() {
+ if (is_environment_blend_mode_supported(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND)) {
+ return OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL;
+ } else if (emulate_environment_blend_mode_alpha_blend) {
+ return OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING;
+ }
+ return OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE;
+}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index efa32b7544..d39e6e0b2e 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -98,13 +98,16 @@ private:
// configuration
XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
- XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ XrReferenceSpaceType requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled.
// blend mode
XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ XrEnvironmentBlendMode requested_environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
uint32_t num_supported_environment_blend_modes = 0;
XrEnvironmentBlendMode *supported_environment_blend_modes = nullptr;
+ bool emulate_environment_blend_mode_alpha_blend = false;
// state
XrInstance instance = XR_NULL_HANDLE;
@@ -149,6 +152,10 @@ private:
bool view_pose_valid = false;
XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
+ bool emulating_local_floor = false;
+ bool should_reset_emulated_floor_height = false;
+ bool reset_emulated_floor_height();
+
bool load_layer_properties();
bool load_supported_extensions();
bool is_extension_supported(const String &p_extension) const;
@@ -227,7 +234,8 @@ private:
bool create_session();
bool load_supported_reference_spaces();
bool is_reference_space_supported(XrReferenceSpaceType p_reference_space);
- bool setup_spaces();
+ bool setup_play_space();
+ bool setup_view_space();
bool load_supported_swapchain_formats();
bool is_swapchain_format_supported(int64_t p_swapchain_format);
bool create_swapchains();
@@ -324,6 +332,7 @@ public:
void set_xr_interface(OpenXRInterface *p_xr_interface);
static void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper);
static void unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper);
+ static const Vector<OpenXRExtensionWrapper *> &get_registered_extension_wrappers();
static void register_extension_metadata();
static void cleanup_extension_wrappers();
@@ -333,7 +342,8 @@ public:
void set_view_configuration(XrViewConfigurationType p_view_configuration);
XrViewConfigurationType get_view_configuration() const { return view_configuration; }
- void set_reference_space(XrReferenceSpaceType p_reference_space);
+ bool set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space);
+ XrReferenceSpaceType get_requested_reference_space() const { return requested_reference_space; }
XrReferenceSpaceType get_reference_space() const { return reference_space; }
void set_submit_depth_buffer(bool p_submit_depth_buffer);
@@ -421,7 +431,16 @@ public:
const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count);
bool is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const;
bool set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode);
- XrEnvironmentBlendMode get_environment_blend_mode() const { return environment_blend_mode; }
+ XrEnvironmentBlendMode get_environment_blend_mode() const { return requested_environment_blend_mode; }
+
+ enum OpenXRAlphaBlendModeSupport {
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE = 0,
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL = 1,
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING = 2,
+ };
+
+ void set_emulate_environment_blend_mode_alpha_blend(bool p_enabled);
+ OpenXRAlphaBlendModeSupport is_environment_blend_mode_alpha_blend_supported();
OpenXRAPI();
~OpenXRAPI();
diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp
index f0f0835f78..d9e282e218 100644
--- a/modules/openxr/openxr_api_extension.cpp
+++ b/modules/openxr/openxr_api_extension.cpp
@@ -30,6 +30,8 @@
#include "openxr_api_extension.h"
+#include "extensions/openxr_extension_wrapper_extension.h"
+
void OpenXRAPIExtension::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance"), &OpenXRAPIExtension::get_instance);
ClassDB::bind_method(D_METHOD("get_system_id"), &OpenXRAPIExtension::get_system_id);
@@ -48,6 +50,16 @@ void OpenXRAPIExtension::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_play_space"), &OpenXRAPIExtension::get_play_space);
ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time);
ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render);
+
+ ClassDB::bind_method(D_METHOD("register_composition_layer_provider", "extension"), &OpenXRAPIExtension::register_composition_layer_provider);
+ ClassDB::bind_method(D_METHOD("unregister_composition_layer_provider", "extension"), &OpenXRAPIExtension::unregister_composition_layer_provider);
+
+ ClassDB::bind_method(D_METHOD("set_emulate_environment_blend_mode_alpha_blend", "enabled"), &OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend);
+ ClassDB::bind_method(D_METHOD("is_environment_blend_mode_alpha_supported"), &OpenXRAPIExtension::is_environment_blend_mode_alpha_blend_supported);
+
+ BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE);
+ BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL);
+ BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING);
}
uint64_t OpenXRAPIExtension::get_instance() {
@@ -126,5 +138,25 @@ bool OpenXRAPIExtension::can_render() {
return OpenXRAPI::get_singleton()->can_render();
}
+void OpenXRAPIExtension::register_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+ OpenXRAPI::get_singleton()->register_composition_layer_provider(p_extension);
+}
+
+void OpenXRAPIExtension::unregister_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+ OpenXRAPI::get_singleton()->unregister_composition_layer_provider(p_extension);
+}
+
+void OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend(bool p_enabled) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+ OpenXRAPI::get_singleton()->set_emulate_environment_blend_mode_alpha_blend(p_enabled);
+}
+
+OpenXRAPIExtension::OpenXRAlphaBlendModeSupport OpenXRAPIExtension::is_environment_blend_mode_alpha_blend_supported() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE);
+ return (OpenXRAPIExtension::OpenXRAlphaBlendModeSupport)OpenXRAPI::get_singleton()->is_environment_blend_mode_alpha_blend_supported();
+}
+
OpenXRAPIExtension::OpenXRAPIExtension() {
}
diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h
index 98f87c7aa1..82344c1d06 100644
--- a/modules/openxr/openxr_api_extension.h
+++ b/modules/openxr/openxr_api_extension.h
@@ -38,6 +38,8 @@
#include "core/os/thread_safe.h"
#include "core/variant/native_ptr.h"
+class OpenXRExtensionWrapperExtension;
+
class OpenXRAPIExtension : public RefCounted {
GDCLASS(OpenXRAPIExtension, RefCounted);
@@ -70,7 +72,21 @@ public:
int64_t get_next_frame_time();
bool can_render();
+ void register_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension);
+ void unregister_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension);
+
+ enum OpenXRAlphaBlendModeSupport {
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE = 0,
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL = 1,
+ OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING = 2,
+ };
+
+ void set_emulate_environment_blend_mode_alpha_blend(bool p_enabled);
+ OpenXRAlphaBlendModeSupport is_environment_blend_mode_alpha_blend_supported();
+
OpenXRAPIExtension();
};
+VARIANT_ENUM_CAST(OpenXRAPIExtension::OpenXRAlphaBlendModeSupport);
+
#endif // OPENXR_API_EXTENSION_H
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 66f8192c9e..05c53ad52f 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -77,6 +77,8 @@ void OpenXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_motion_range", "hand", "motion_range"), &OpenXRInterface::set_motion_range);
ClassDB::bind_method(D_METHOD("get_motion_range", "hand"), &OpenXRInterface::get_motion_range);
+ ClassDB::bind_method(D_METHOD("get_hand_tracking_source", "hand"), &OpenXRInterface::get_hand_tracking_source);
+
ClassDB::bind_method(D_METHOD("get_hand_joint_flags", "hand", "joint"), &OpenXRInterface::get_hand_joint_flags);
ClassDB::bind_method(D_METHOD("get_hand_joint_rotation", "hand", "joint"), &OpenXRInterface::get_hand_joint_rotation);
@@ -97,6 +99,11 @@ void OpenXRInterface::_bind_methods() {
BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER);
BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_MAX);
+ BIND_ENUM_CONSTANT(HAND_TRACKED_SOURCE_UNKNOWN);
+ BIND_ENUM_CONSTANT(HAND_TRACKED_SOURCE_UNOBSTRUCTED);
+ BIND_ENUM_CONSTANT(HAND_TRACKED_SOURCE_CONTROLLER);
+ BIND_ENUM_CONSTANT(HAND_TRACKED_SOURCE_MAX);
+
BIND_ENUM_CONSTANT(HAND_JOINT_PALM);
BIND_ENUM_CONSTANT(HAND_JOINT_WRIST);
BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_METACARPAL);
@@ -679,14 +686,53 @@ Dictionary OpenXRInterface::get_system_info() {
}
bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
- return false;
+ if (p_mode == XRInterface::XR_PLAY_AREA_3DOF) {
+ return false;
+ }
+ return true;
}
XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const {
+ if (!openxr_api || !initialized) {
+ return XRInterface::XR_PLAY_AREA_UNKNOWN;
+ }
+
+ XrReferenceSpaceType reference_space = openxr_api->get_reference_space();
+
+ if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL) {
+ return XRInterface::XR_PLAY_AREA_SITTING;
+ } else if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) {
+ return XRInterface::XR_PLAY_AREA_ROOMSCALE;
+ } else if (reference_space == XR_REFERENCE_SPACE_TYPE_STAGE) {
+ return XRInterface::XR_PLAY_AREA_STAGE;
+ }
+
return XRInterface::XR_PLAY_AREA_UNKNOWN;
}
bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
+ ERR_FAIL_NULL_V(openxr_api, false);
+
+ XrReferenceSpaceType reference_space;
+
+ if (p_mode == XRInterface::XR_PLAY_AREA_SITTING) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ } else if (p_mode == XRInterface::XR_PLAY_AREA_ROOMSCALE) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+ } else if (p_mode == XRInterface::XR_PLAY_AREA_STAGE) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ } else {
+ return false;
+ }
+
+ if (openxr_api->set_requested_reference_space(reference_space)) {
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server) {
+ xr_server->clear_reference_frame();
+ }
+ return true;
+ }
+
return false;
}
@@ -1112,21 +1158,19 @@ void OpenXRInterface::end_frame() {
}
bool OpenXRInterface::is_passthrough_supported() {
- return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported();
+ return get_supported_environment_blend_modes().find(XR_ENV_BLEND_MODE_ALPHA_BLEND);
}
bool OpenXRInterface::is_passthrough_enabled() {
- return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled();
+ return get_environment_blend_mode() == XR_ENV_BLEND_MODE_ALPHA_BLEND;
}
bool OpenXRInterface::start_passthrough() {
- return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough();
+ return set_environment_blend_mode(XR_ENV_BLEND_MODE_ALPHA_BLEND);
}
void OpenXRInterface::stop_passthrough() {
- if (passthrough_wrapper) {
- passthrough_wrapper->stop_passthrough();
- }
+ set_environment_blend_mode(XR_ENV_BLEND_MODE_OPAQUE);
}
Array OpenXRInterface::get_supported_environment_blend_modes() {
@@ -1158,6 +1202,11 @@ Array OpenXRInterface::get_supported_environment_blend_modes() {
WARN_PRINT("Unsupported blend mode found: " + String::num_int64(int64_t(env_blend_modes[i])));
}
}
+
+ if (openxr_api->is_environment_blend_mode_alpha_blend_supported() == OpenXRAPI::OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING) {
+ modes.push_back(XR_ENV_BLEND_MODE_ALPHA_BLEND);
+ }
+
return modes;
}
@@ -1269,6 +1318,27 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
return HAND_MOTION_RANGE_MAX;
}
+OpenXRInterface::HandTrackedSource OpenXRInterface::get_hand_tracking_source(const Hand p_hand) const {
+ ERR_FAIL_INDEX_V(p_hand, HAND_MAX, HAND_TRACKED_SOURCE_UNKNOWN);
+
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ OpenXRHandTrackingExtension::HandTrackedSource source = hand_tracking_ext->get_hand_tracking_source(OpenXRHandTrackingExtension::HandTrackedHands(p_hand));
+ switch (source) {
+ case OpenXRHandTrackingExtension::OPENXR_SOURCE_UNOBSTRUCTED:
+ return HAND_TRACKED_SOURCE_UNOBSTRUCTED;
+ case OpenXRHandTrackingExtension::OPENXR_SOURCE_CONTROLLER:
+ return HAND_TRACKED_SOURCE_CONTROLLER;
+ case OpenXRHandTrackingExtension::OPENXR_SOURCE_UNKNOWN:
+ return HAND_TRACKED_SOURCE_UNKNOWN;
+ default:
+ ERR_FAIL_V_MSG(HAND_TRACKED_SOURCE_UNKNOWN, "Unknown hand tracking source returned by OpenXR");
+ }
+ }
+
+ return HAND_TRACKED_SOURCE_UNKNOWN;
+}
+
BitField<OpenXRInterface::HandJointFlags> OpenXRInterface::get_hand_joint_flags(Hand p_hand, HandJoints p_joint) const {
BitField<OpenXRInterface::HandJointFlags> bits;
@@ -1355,8 +1425,6 @@ OpenXRInterface::OpenXRInterface() {
_set_default_pos(head_transform, 1.0, 0);
_set_default_pos(transform_for_view[0], 1.0, 1);
_set_default_pos(transform_for_view[1], 1.0, 2);
-
- passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton();
}
OpenXRInterface::~OpenXRInterface() {
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 489d0845ba..aee9751d6b 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -32,7 +32,6 @@
#define OPENXR_INTERFACE_H
#include "action_map/openxr_action_map.h"
-#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_hand_tracking_extension.h"
#include "openxr_api.h"
@@ -49,7 +48,6 @@ private:
OpenXRAPI *openxr_api = nullptr;
bool initialized = false;
XRInterface::TrackingStatus tracking_state;
- OpenXRFbPassthroughExtensionWrapper *passthrough_wrapper = nullptr;
// At a minimum we need a tracker for our head
Ref<XRPositionalTracker> head;
@@ -194,6 +192,15 @@ public:
void set_motion_range(const Hand p_hand, const HandMotionRange p_motion_range);
HandMotionRange get_motion_range(const Hand p_hand) const;
+ enum HandTrackedSource {
+ HAND_TRACKED_SOURCE_UNKNOWN,
+ HAND_TRACKED_SOURCE_UNOBSTRUCTED,
+ HAND_TRACKED_SOURCE_CONTROLLER,
+ HAND_TRACKED_SOURCE_MAX
+ };
+
+ HandTrackedSource get_hand_tracking_source(const Hand p_hand) const;
+
enum HandJoints {
HAND_JOINT_PALM = 0,
HAND_JOINT_WRIST = 1,
@@ -248,6 +255,7 @@ public:
VARIANT_ENUM_CAST(OpenXRInterface::Hand)
VARIANT_ENUM_CAST(OpenXRInterface::HandMotionRange)
+VARIANT_ENUM_CAST(OpenXRInterface::HandTrackedSource)
VARIANT_ENUM_CAST(OpenXRInterface::HandJoints)
VARIANT_BITFIELD_CAST(OpenXRInterface::HandJointFlags)
diff --git a/modules/openxr/openxr_platform_inc.h b/modules/openxr/openxr_platform_inc.h
index 6288d1e380..957a87cbb2 100644
--- a/modules/openxr/openxr_platform_inc.h
+++ b/modules/openxr/openxr_platform_inc.h
@@ -36,7 +36,7 @@
#ifdef VULKAN_ENABLED
#define XR_USE_GRAPHICS_API_VULKAN
-#include "drivers/vulkan/vulkan_context.h"
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif // VULKAN_ENABLED
#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 544932bdeb..a0a93c8694 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -44,11 +44,12 @@
#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
-#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_hand_tracking_extension.h"
#include "extensions/openxr_htc_controller_extension.h"
#include "extensions/openxr_htc_vive_tracker_extension.h"
#include "extensions/openxr_huawei_controller_extension.h"
+#include "extensions/openxr_local_floor_extension.h"
+#include "extensions/openxr_meta_controller_extension.h"
#include "extensions/openxr_ml2_controller_extension.h"
#include "extensions/openxr_palm_pose_extension.h"
#include "extensions/openxr_pico_controller_extension.h"
@@ -59,7 +60,7 @@
#endif
#ifdef ANDROID_ENABLED
-#include "extensions/openxr_android_extension.h"
+#include "extensions/platform/openxr_android_extension.h"
#endif
#include "core/config/project_settings.h"
@@ -106,15 +107,16 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
// register our other extensions
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension));
- OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension));
// register gated extensions
if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) {
diff --git a/modules/openxr/scene/SCsub b/modules/openxr/scene/SCsub
new file mode 100644
index 0000000000..7a493011ec
--- /dev/null
+++ b/modules/openxr/scene/SCsub
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_openxr")
+
+module_obj = []
+
+env_openxr.add_source_files(module_obj, "*.cpp")
+
+env.modules_sources += module_obj
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index c48fac8055..2a4104f6ee 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -46,9 +46,17 @@ void OpenXRHand::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_motion_range", "motion_range"), &OpenXRHand::set_motion_range);
ClassDB::bind_method(D_METHOD("get_motion_range"), &OpenXRHand::get_motion_range);
+ ClassDB::bind_method(D_METHOD("set_skeleton_rig", "skeleton_rig"), &OpenXRHand::set_skeleton_rig);
+ ClassDB::bind_method(D_METHOD("get_skeleton_rig"), &OpenXRHand::get_skeleton_rig);
+
+ ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &OpenXRHand::set_bone_update);
+ ClassDB::bind_method(D_METHOD("get_bone_update"), &OpenXRHand::get_bone_update);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand");
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton_rig", PROPERTY_HINT_ENUM, "OpenXR,Humanoid"), "set_skeleton_rig", "get_skeleton_rig");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
BIND_ENUM_CONSTANT(HAND_LEFT);
BIND_ENUM_CONSTANT(HAND_RIGHT);
@@ -57,6 +65,14 @@ void OpenXRHand::_bind_methods() {
BIND_ENUM_CONSTANT(MOTION_RANGE_UNOBSTRUCTED);
BIND_ENUM_CONSTANT(MOTION_RANGE_CONFORM_TO_CONTROLLER);
BIND_ENUM_CONSTANT(MOTION_RANGE_MAX);
+
+ BIND_ENUM_CONSTANT(SKELETON_RIG_OPENXR);
+ BIND_ENUM_CONSTANT(SKELETON_RIG_HUMANOID);
+ BIND_ENUM_CONSTANT(SKELETON_RIG_MAX);
+
+ BIND_ENUM_CONSTANT(BONE_UPDATE_FULL);
+ BIND_ENUM_CONSTANT(BONE_UPDATE_ROTATION_ONLY);
+ BIND_ENUM_CONSTANT(BONE_UPDATE_MAX);
}
OpenXRHand::OpenXRHand() {
@@ -64,7 +80,7 @@ OpenXRHand::OpenXRHand() {
hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
}
-void OpenXRHand::set_hand(const Hands p_hand) {
+void OpenXRHand::set_hand(Hands p_hand) {
ERR_FAIL_INDEX(p_hand, HAND_MAX);
hand = p_hand;
@@ -80,7 +96,7 @@ void OpenXRHand::set_hand_skeleton(const NodePath &p_hand_skeleton) {
// TODO if inside tree call _get_bones()
}
-void OpenXRHand::set_motion_range(const MotionRange p_motion_range) {
+void OpenXRHand::set_motion_range(MotionRange p_motion_range) {
ERR_FAIL_INDEX(p_motion_range, MOTION_RANGE_MAX);
motion_range = p_motion_range;
@@ -116,6 +132,26 @@ void OpenXRHand::_set_motion_range() {
hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(hand), xr_motion_range);
}
+void OpenXRHand::set_skeleton_rig(SkeletonRig p_skeleton_rig) {
+ ERR_FAIL_INDEX(p_skeleton_rig, SKELETON_RIG_MAX);
+
+ skeleton_rig = p_skeleton_rig;
+}
+
+OpenXRHand::SkeletonRig OpenXRHand::get_skeleton_rig() const {
+ return skeleton_rig;
+}
+
+void OpenXRHand::set_bone_update(BoneUpdate p_bone_update) {
+ ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX);
+
+ bone_update = p_bone_update;
+}
+
+OpenXRHand::BoneUpdate OpenXRHand::get_bone_update() const {
+ return bone_update;
+}
+
Skeleton3D *OpenXRHand::get_skeleton() {
if (!has_node(hand_skeleton)) {
return nullptr;
@@ -130,39 +166,81 @@ Skeleton3D *OpenXRHand::get_skeleton() {
return skeleton;
}
-void OpenXRHand::_get_bones() {
- const char *bone_names[XR_HAND_JOINT_COUNT_EXT] = {
- "Palm",
- "Wrist",
- "Thumb_Metacarpal",
- "Thumb_Proximal",
- "Thumb_Distal",
- "Thumb_Tip",
- "Index_Metacarpal",
- "Index_Proximal",
- "Index_Intermediate",
- "Index_Distal",
- "Index_Tip",
- "Middle_Metacarpal",
- "Middle_Proximal",
- "Middle_Intermediate",
- "Middle_Distal",
- "Middle_Tip",
- "Ring_Metacarpal",
- "Ring_Proximal",
- "Ring_Intermediate",
- "Ring_Distal",
- "Ring_Tip",
- "Little_Metacarpal",
- "Little_Proximal",
- "Little_Intermediate",
- "Little_Distal",
- "Little_Tip",
+void OpenXRHand::_get_joint_data() {
+ // Table of bone names for different rig types.
+ static const String bone_names[SKELETON_RIG_MAX][XR_HAND_JOINT_COUNT_EXT] = {
+ // SKELETON_RIG_OPENXR bone names.
+ {
+ "Palm",
+ "Wrist",
+ "Thumb_Metacarpal",
+ "Thumb_Proximal",
+ "Thumb_Distal",
+ "Thumb_Tip",
+ "Index_Metacarpal",
+ "Index_Proximal",
+ "Index_Intermediate",
+ "Index_Distal",
+ "Index_Tip",
+ "Middle_Metacarpal",
+ "Middle_Proximal",
+ "Middle_Intermediate",
+ "Middle_Distal",
+ "Middle_Tip",
+ "Ring_Metacarpal",
+ "Ring_Proximal",
+ "Ring_Intermediate",
+ "Ring_Distal",
+ "Ring_Tip",
+ "Little_Metacarpal",
+ "Little_Proximal",
+ "Little_Intermediate",
+ "Little_Distal",
+ "Little_Tip" },
+
+ // SKELETON_RIG_HUMANOID bone names.
+ {
+ "Palm",
+ "Hand",
+ "ThumbMetacarpal",
+ "ThumbProximal",
+ "ThumbDistal",
+ "ThumbTip",
+ "IndexMetacarpal",
+ "IndexProximal",
+ "IndexIntermediate",
+ "IndexDistal",
+ "IndexTip",
+ "MiddleMetacarpal",
+ "MiddleProximal",
+ "MiddleIntermediate",
+ "MiddleDistal",
+ "MiddleTip",
+ "RingMetacarpal",
+ "RingProximal",
+ "RingIntermediate",
+ "RingDistal",
+ "RingTip",
+ "LittleMetacarpal",
+ "LittleProximal",
+ "LittleIntermediate",
+ "LittleDistal",
+ "LittleTip" }
+ };
+
+ // Table of bone name formats for different rig types and left/right hands.
+ static const String bone_name_formats[SKELETON_RIG_MAX][2] = {
+ // SKELETON_RIG_OPENXR bone name format.
+ { "<bone>_L", "<bone>_R" },
+
+ // SKELETON_RIG_HUMANOID bone name format.
+ { "Left<bone>", "Right<bone>" }
};
// reset JIC
for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
- bones[i] = -1;
+ joints[i].bone = -1;
+ joints[i].parent_joint = -1;
}
Skeleton3D *skeleton = get_skeleton();
@@ -170,20 +248,46 @@ void OpenXRHand::_get_bones() {
return;
}
- // We cast to spatials which should allow us to use any subclass of that.
+ // Find the skeleton-bones associated with each OpenXR joint.
+ int bones[XR_HAND_JOINT_COUNT_EXT];
for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
- String bone_name = bone_names[i];
- if (hand == 0) {
- bone_name += String("_L");
- } else {
- bone_name += String("_R");
- }
+ // Construct the expected bone name.
+ String bone_name = bone_name_formats[skeleton_rig][hand].replace("<bone>", bone_names[skeleton_rig][i]);
+ // Find the skeleton bone.
bones[i] = skeleton->find_bone(bone_name);
if (bones[i] == -1) {
print_line("Couldn't obtain bone for", bone_name);
}
}
+
+ // Assemble the OpenXR joint relationship to the available skeleton bones.
+ for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
+ // Get the skeleton bone (skip if not found).
+ const int bone = bones[i];
+ if (bone == -1) {
+ continue;
+ }
+
+ // Find the parent skeleton-bone.
+ const int parent_bone = skeleton->get_bone_parent(bone);
+ if (parent_bone == -1) {
+ // If no parent skeleton-bone exists then drive this relative to palm joint.
+ joints[i].bone = bone;
+ joints[i].parent_joint = XR_HAND_JOINT_PALM_EXT;
+ continue;
+ }
+
+ // Find the OpenXR joint associated with the parent skeleton-bone.
+ for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; ++j) {
+ if (bones[j] == parent_bone) {
+ // If a parent joint is found then drive this bone relative to it.
+ joints[i].bone = bone;
+ joints[i].parent_joint = j;
+ break;
+ }
+ }
+ }
}
void OpenXRHand::_update_skeleton() {
@@ -198,12 +302,25 @@ void OpenXRHand::_update_skeleton() {
return;
}
+ // Table of bone adjustments for different rig types
+ static const Quaternion bone_adjustments[SKELETON_RIG_MAX] = {
+ // SKELETON_RIG_OPENXR bone adjustment. This is an identity quaternion
+ // because the incoming quaternions are already in OpenXR format.
+ Quaternion(),
+
+ // SKELETON_RIG_HUMANOID bone adjustment. This rotation performs:
+ // OpenXR Z+ -> Godot Humanoid Y- (Back along the bone)
+ // OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand)
+ Quaternion(0.0, -Math_SQRT12, Math_SQRT12, 0.0),
+ };
+
// we cache our transforms so we can quickly calculate local transforms
XRPose::TrackingConfidence confidences[XR_HAND_JOINT_COUNT_EXT];
Quaternion quaternions[XR_HAND_JOINT_COUNT_EXT];
Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT];
Vector3 positions[XR_HAND_JOINT_COUNT_EXT];
+ const Quaternion &rig_adjustment = bone_adjustments[skeleton_rig];
const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(OpenXRHandTrackingExtension::HandTrackedHands(hand));
const float ws = XRServer::get_singleton()->get_world_scale();
@@ -218,7 +335,7 @@ void OpenXRHand::_update_skeleton() {
if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
if (pose.orientation.x != 0 || pose.orientation.y != 0 || pose.orientation.z != 0 || pose.orientation.w != 0) {
- quaternions[i] = Quaternion(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w);
+ quaternions[i] = Quaternion(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w) * rig_adjustment;
inv_quaternions[i] = quaternions[i].inverse();
if (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
@@ -234,40 +351,29 @@ void OpenXRHand::_update_skeleton() {
}
if (confidences[XR_HAND_JOINT_PALM_EXT] != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
- // now update our skeleton
- for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
- if (bones[i] != -1) {
- int bone = bones[i];
- int parent = skeleton->get_bone_parent(bone);
-
- // Get our target quaternion
- Quaternion q = quaternions[i];
-
- // Get our target position
- Vector3 p = positions[i];
+ // Iterate over all the OpenXR joints.
+ for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) {
+ // Get the skeleton bone (skip if none).
+ const int bone = joints[joint].bone;
+ if (bone == -1) {
+ continue;
+ }
- // get local translation, parent should already be processed
- if (parent == -1) {
- // use our palm location here, that is what we are tracking
- q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q;
- p = inv_quaternions[XR_HAND_JOINT_PALM_EXT].xform(p - positions[XR_HAND_JOINT_PALM_EXT]);
- } else {
- int found = false;
- for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) {
- if (bones[b] == parent) {
- q = inv_quaternions[b] * q;
- p = inv_quaternions[b].xform(p - positions[b]);
- found = true;
- }
- }
- }
+ // Calculate the relative relationship to the parent bone joint.
+ const int parent_joint = joints[joint].parent_joint;
+ const Quaternion q = inv_quaternions[parent_joint] * quaternions[joint];
+ const Vector3 p = inv_quaternions[parent_joint].xform(positions[joint] - positions[parent_joint]);
- // and set our pose
- skeleton->set_bone_pose_position(bones[i], p);
- skeleton->set_bone_pose_rotation(bones[i], q);
+ // Update the bone position if enabled by update mode.
+ if (bone_update == BONE_UPDATE_FULL) {
+ skeleton->set_bone_pose_position(joints[joint].bone, p);
}
+
+ // Always update the bone rotation.
+ skeleton->set_bone_pose_rotation(joints[joint].bone, q);
}
+ // Transform the OpenXRHand to the skeleton pose.
Transform3D t;
t.basis = Basis(quaternions[XR_HAND_JOINT_PALM_EXT]);
t.origin = positions[XR_HAND_JOINT_PALM_EXT];
@@ -288,7 +394,7 @@ void OpenXRHand::_update_skeleton() {
void OpenXRHand::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- _get_bones();
+ _get_joint_data();
set_process_internal(true);
} break;
@@ -297,7 +403,8 @@ void OpenXRHand::_notification(int p_what) {
// reset
for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
- bones[i] = -1;
+ joints[i].bone = -1;
+ joints[i].parent_joint = -1;
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h
index edfb474ac7..4c77e7277c 100644
--- a/modules/openxr/scene/openxr_hand.h
+++ b/modules/openxr/scene/openxr_hand.h
@@ -55,20 +55,39 @@ public:
MOTION_RANGE_MAX
};
+ enum SkeletonRig {
+ SKELETON_RIG_OPENXR,
+ SKELETON_RIG_HUMANOID,
+ SKELETON_RIG_MAX
+ };
+
+ enum BoneUpdate {
+ BONE_UPDATE_FULL,
+ BONE_UPDATE_ROTATION_ONLY,
+ BONE_UPDATE_MAX
+ };
+
private:
+ struct JointData {
+ int bone = -1;
+ int parent_joint = -1;
+ };
+
OpenXRAPI *openxr_api = nullptr;
OpenXRHandTrackingExtension *hand_tracking_ext = nullptr;
Hands hand = HAND_LEFT;
MotionRange motion_range = MOTION_RANGE_UNOBSTRUCTED;
NodePath hand_skeleton;
+ SkeletonRig skeleton_rig = SKELETON_RIG_OPENXR;
+ BoneUpdate bone_update = BONE_UPDATE_FULL;
- int64_t bones[XR_HAND_JOINT_COUNT_EXT];
+ JointData joints[XR_HAND_JOINT_COUNT_EXT];
void _set_motion_range();
Skeleton3D *get_skeleton();
- void _get_bones();
+ void _get_joint_data();
void _update_skeleton();
protected:
@@ -77,19 +96,27 @@ protected:
public:
OpenXRHand();
- void set_hand(const Hands p_hand);
+ void set_hand(Hands p_hand);
Hands get_hand() const;
- void set_motion_range(const MotionRange p_motion_range);
+ void set_motion_range(MotionRange p_motion_range);
MotionRange get_motion_range() const;
void set_hand_skeleton(const NodePath &p_hand_skeleton);
NodePath get_hand_skeleton() const;
+ void set_skeleton_rig(SkeletonRig p_skeleton_rig);
+ SkeletonRig get_skeleton_rig() const;
+
+ void set_bone_update(BoneUpdate p_bone_update);
+ BoneUpdate get_bone_update() const;
+
void _notification(int p_what);
};
VARIANT_ENUM_CAST(OpenXRHand::Hands)
VARIANT_ENUM_CAST(OpenXRHand::MotionRange)
+VARIANT_ENUM_CAST(OpenXRHand::SkeletonRig)
+VARIANT_ENUM_CAST(OpenXRHand::BoneUpdate)
#endif // OPENXR_HAND_H
diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp
index 704c107f20..d49578d2a9 100644
--- a/modules/regex/regex.cpp
+++ b/modules/regex/regex.cpp
@@ -270,16 +270,18 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end)
TypedArray<RegExMatch> RegEx::search_all(const String &p_subject, int p_offset, int p_end) const {
ERR_FAIL_COND_V_MSG(p_offset < 0, Array(), "RegEx search offset must be >= 0");
- int last_end = -1;
+ int last_end = 0;
TypedArray<RegExMatch> result;
Ref<RegExMatch> match = search(p_subject, p_offset, p_end);
+
while (match.is_valid()) {
- if (last_end == match->get_end(0)) {
- break;
+ last_end = match->get_end(0);
+ if (match->get_start(0) == last_end) {
+ last_end++;
}
+
result.push_back(match);
- last_end = match->get_end(0);
- match = search(p_subject, match->get_end(0), p_end);
+ match = search(p_subject, last_end, p_end);
}
return result;
}
diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h
index 3e4d769377..0b401da831 100644
--- a/modules/regex/tests/test_regex.h
+++ b/modules/regex/tests/test_regex.h
@@ -164,7 +164,7 @@ TEST_CASE("[RegEx] Uninitialized use") {
ERR_PRINT_ON
}
-TEST_CASE("[RegEx] Empty Pattern") {
+TEST_CASE("[RegEx] Empty pattern") {
const String s = "Godot";
RegEx re;
@@ -222,6 +222,143 @@ TEST_CASE("[RegEx] Match start and end positions") {
CHECK(match->get_start("vowel") == 2);
CHECK(match->get_end("vowel") == 3);
}
+
+TEST_CASE("[RegEx] Asterisk search all") {
+ const String s = "Godot Engine";
+
+ RegEx re("o*");
+ REQUIRE(re.is_valid());
+ Ref<RegExMatch> match;
+ const Array all_results = re.search_all(s);
+ CHECK(all_results.size() == 13);
+
+ match = all_results[0];
+ CHECK(match->get_string(0) == "");
+ match = all_results[1];
+ CHECK(match->get_string(0) == "o");
+ match = all_results[2];
+ CHECK(match->get_string(0) == "");
+ match = all_results[3];
+ CHECK(match->get_string(0) == "o");
+
+ for (int i = 4; i < 13; i++) {
+ match = all_results[i];
+ CHECK(match->get_string(0) == "");
+ }
+}
+
+TEST_CASE("[RegEx] Simple lookahead") {
+ const String s = "Godot Engine";
+
+ RegEx re("o(?=t)");
+ REQUIRE(re.is_valid());
+ Ref<RegExMatch> match = re.search(s);
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 3);
+ CHECK(match->get_end(0) == 4);
+}
+
+TEST_CASE("[RegEx] Lookahead groups empty matches") {
+ const String s = "12";
+
+ RegEx re("(?=(\\d+))");
+ REQUIRE(re.is_valid());
+ Ref<RegExMatch> match = re.search(s);
+ CHECK(match->get_string(0) == "");
+ CHECK(match->get_string(1) == "12");
+
+ const Array all_results = re.search_all(s);
+ CHECK(all_results.size() == 2);
+
+ match = all_results[0];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_string(0) == String(""));
+ CHECK(match->get_string(1) == String("12"));
+
+ match = all_results[1];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_string(0) == String(""));
+ CHECK(match->get_string(1) == String("2"));
+}
+
+TEST_CASE("[RegEx] Simple lookbehind") {
+ const String s = "Godot Engine";
+
+ RegEx re("(?<=d)o");
+ REQUIRE(re.is_valid());
+ Ref<RegExMatch> match = re.search(s);
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 3);
+ CHECK(match->get_end(0) == 4);
+}
+
+TEST_CASE("[RegEx] Simple lookbehind search all") {
+ const String s = "ababbaabab";
+
+ RegEx re("(?<=a)b");
+ REQUIRE(re.is_valid());
+ const Array all_results = re.search_all(s);
+ CHECK(all_results.size() == 4);
+
+ Ref<RegExMatch> match = all_results[0];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 1);
+ CHECK(match->get_end(0) == 2);
+
+ match = all_results[1];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 3);
+ CHECK(match->get_end(0) == 4);
+
+ match = all_results[2];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 7);
+ CHECK(match->get_end(0) == 8);
+
+ match = all_results[3];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 9);
+ CHECK(match->get_end(0) == 10);
+}
+
+TEST_CASE("[RegEx] Lookbehind groups empty matches") {
+ const String s = "abaaabab";
+
+ RegEx re("(?<=(b))");
+ REQUIRE(re.is_valid());
+ Ref<RegExMatch> match;
+
+ const Array all_results = re.search_all(s);
+ CHECK(all_results.size() == 3);
+
+ match = all_results[0];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 2);
+ CHECK(match->get_end(0) == 2);
+ CHECK(match->get_start(1) == 1);
+ CHECK(match->get_end(1) == 2);
+ CHECK(match->get_string(0) == String(""));
+ CHECK(match->get_string(1) == String("b"));
+
+ match = all_results[1];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 6);
+ CHECK(match->get_end(0) == 6);
+ CHECK(match->get_start(1) == 5);
+ CHECK(match->get_end(1) == 6);
+ CHECK(match->get_string(0) == String(""));
+ CHECK(match->get_string(1) == String("b"));
+
+ match = all_results[2];
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 8);
+ CHECK(match->get_end(0) == 8);
+ CHECK(match->get_start(1) == 7);
+ CHECK(match->get_end(1) == 8);
+ CHECK(match->get_string(0) == String(""));
+ CHECK(match->get_string(1) == String("b"));
+}
+
} // namespace TestRegEx
#endif // TEST_REGEX_H
diff --git a/modules/svg/SCsub b/modules/svg/SCsub
index d0c6250b11..ff0c3c9141 100644
--- a/modules/svg/SCsub
+++ b/modules/svg/SCsub
@@ -43,6 +43,8 @@ thirdparty_sources = [
"src/renderer/tvgShape.cpp",
"src/renderer/tvgSwCanvas.cpp",
"src/renderer/tvgTaskScheduler.cpp",
+ "src/renderer/tvgText.cpp",
+ # "src/renderer/tvgWgCanvas.cpp",
# renderer sw_engine
"src/renderer/sw_engine/tvgSwFill.cpp",
"src/renderer/sw_engine/tvgSwImage.cpp",
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index a542bbc234..affe163aeb 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -72,7 +72,7 @@ Ref<Image> ImageLoaderSVG::load_mem_svg(const uint8_t *p_svg, int p_size, float
img.instantiate();
Error err = create_image_from_utf8_buffer(img, p_svg, p_size, p_scale, false);
- ERR_FAIL_COND_V(err, Ref<Image>());
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), vformat("ImageLoaderSVG: Failed to create SVG from buffer, error code %d.", err));
return img;
}
diff --git a/modules/svg/register_types.cpp b/modules/svg/register_types.cpp
index 7b61749f61..82d816d833 100644
--- a/modules/svg/register_types.cpp
+++ b/modules/svg/register_types.cpp
@@ -34,6 +34,12 @@
#include <thorvg.h>
+#ifdef THREADS_ENABLED
+#define TVG_THREADS 1
+#else
+#define TVG_THREADS 0
+#endif
+
static Ref<ImageLoaderSVG> image_loader_svg;
void initialize_svg_module(ModuleInitializationLevel p_level) {
@@ -42,7 +48,8 @@ void initialize_svg_module(ModuleInitializationLevel p_level) {
}
tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw;
- if (tvg::Initializer::init(tvgEngine, 1) != tvg::Result::Success) {
+
+ if (tvg::Initializer::init(tvgEngine, TVG_THREADS) != tvg::Result::Success) {
return;
}
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 3c468e61d7..09d9b2dd67 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -401,6 +401,8 @@ if env["builtin_icu4c"]:
"common/uloc.cpp",
"common/uloc_keytype.cpp",
"common/uloc_tag.cpp",
+ "common/ulocale.cpp",
+ "common/ulocbuilder.cpp",
"common/umapfile.cpp",
"common/umath.cpp",
"common/umutablecptrie.cpp",
@@ -466,7 +468,7 @@ if env["builtin_icu4c"]:
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
- icu_data_name = "icudt73l.dat"
+ icu_data_name = "icudt74l.dat"
if env.editor_build:
env_icu.Depends("#thirdparty/icu4c/icudata.gen.h", "#thirdparty/icu4c/" + icu_data_name)
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 4093842650..cd4331b60e 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -74,6 +74,8 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/renderer/tvgShape.cpp",
"src/renderer/tvgSwCanvas.cpp",
"src/renderer/tvgTaskScheduler.cpp",
+ "src/renderer/tvgText.cpp",
+ # "src/renderer/tvgWgCanvas.cpp",
# renderer sw_engine
"src/renderer/sw_engine/tvgSwFill.cpp",
"src/renderer/sw_engine/tvgSwImage.cpp",
@@ -623,6 +625,8 @@ thirdparty_icu_sources = [
"common/uloc.cpp",
"common/uloc_keytype.cpp",
"common/uloc_tag.cpp",
+ "common/ulocale.cpp",
+ "common/ulocbuilder.cpp",
"common/umapfile.cpp",
"common/umath.cpp",
"common/umutablecptrie.cpp",
@@ -688,7 +692,7 @@ thirdparty_icu_sources = [
]
thirdparty_icu_sources = [thirdparty_icu_dir + file for file in thirdparty_icu_sources]
-icu_data_name = "icudt73l.dat"
+icu_data_name = "icudt74l.dat"
if env["static_icu_data"]:
env_icu.Depends("../../../thirdparty/icu4c/icudata.gen.h", "../../../thirdparty/icu4c/" + icu_data_name)
diff --git a/modules/text_server_adv/register_types.h b/modules/text_server_adv/register_types.h
index c11765048d..477e030e03 100644
--- a/modules/text_server_adv/register_types.h
+++ b/modules/text_server_adv/register_types.h
@@ -34,7 +34,7 @@
#ifdef GDEXTENSION
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
#include "modules/register_module_types.h"
#endif
diff --git a/modules/text_server_adv/script_iterator.h b/modules/text_server_adv/script_iterator.h
index 164fdec784..f7876d6cbc 100644
--- a/modules/text_server_adv/script_iterator.h
+++ b/modules/text_server_adv/script_iterator.h
@@ -40,7 +40,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/string/ustring.h"
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index bcc7bff909..4c460a002f 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -44,7 +44,7 @@ using namespace godot;
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get_setting_with_override(m_var)
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/config/project_settings.h"
@@ -370,7 +370,7 @@ bool TextServerAdvanced::_has_feature(Feature p_feature) const {
String TextServerAdvanced::_get_name() const {
#ifdef GDEXTENSION
return "ICU / HarfBuzz / Graphite (GDExtension)";
-#else
+#elif defined(GODOT_MODULE)
return "ICU / HarfBuzz / Graphite (Built-in)";
#endif
}
@@ -2409,6 +2409,37 @@ int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType
}
}
+void TextServerAdvanced::_font_set_baseline_offset(const RID &p_font_rid, float p_baseline_offset) {
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->baseline_offset != p_baseline_offset) {
+ fdv->baseline_offset = p_baseline_offset;
+ }
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->baseline_offset != p_baseline_offset) {
+ _font_clear_cache(fd);
+ fd->baseline_offset = p_baseline_offset;
+ }
+ }
+}
+
+float TextServerAdvanced::_font_get_baseline_offset(const RID &p_font_rid) const {
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->baseline_offset;
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0.0);
+
+ MutexLock lock(fd->mutex);
+ return fd->baseline_offset;
+ }
+}
+
void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -3243,7 +3274,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
- for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : fd->cache) {
+ for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
ret.push_back(E.key);
}
return ret;
@@ -4852,7 +4883,7 @@ RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
-#else
+#elif defined(GODOT_MODULE)
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, p_fdef, this);
@@ -5750,6 +5781,11 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char
gl.x_off = Math::round((double)glyph_pos[0].x_offset / (64.0 / scale));
}
gl.y_off = -Math::round((double)glyph_pos[0].y_offset / (64.0 / scale));
+ if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
+ gl.y_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
+ } else {
+ gl.x_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
+ }
if ((glyph_info[0].codepoint != 0) || !u_isgraph(p_char)) {
gl.flags |= GRAPHEME_IS_VALID;
@@ -5964,6 +6000,11 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
gl.x_off = Math::round((double)glyph_pos[i].x_offset / (64.0 / scale));
}
gl.y_off = -Math::round((double)glyph_pos[i].y_offset / (64.0 / scale));
+ if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
+ gl.y_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
+ } else {
+ gl.x_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
+ }
}
if (!last_run || i < glyph_count - 1) {
// Do not add extra spacing to the last glyph of the string.
@@ -6757,7 +6798,7 @@ String TextServerAdvanced::_strip_diacritics(const String &p_string) const {
if (u_getCombiningClass(normalized_string[i]) == 0) {
#ifdef GDEXTENSION
result = result + String::chr(normalized_string[i]);
-#else
+#elif defined(GODOT_MODULE)
result = result + normalized_string[i];
#endif
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index e5d0ab3105..6f53ecfa8f 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -78,7 +78,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/extension/ext_wrappers.gen.inc"
@@ -297,6 +297,7 @@ class TextServerAdvanced : public TextServerExtension {
struct FontAdvancedLinkedVariation {
RID base_font;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
};
struct FontAdvanced {
@@ -324,6 +325,7 @@ class TextServerAdvanced : public TextServerExtension {
int weight = 400;
int stretch = 100;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache;
@@ -574,9 +576,10 @@ class TextServerAdvanced : public TextServerExtension {
double embolden = 0.0;
Transform2D transform;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
bool operator==(const SystemFontKey &p_b) const {
- return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]);
+ return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset);
}
SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerAdvanced *p_fb) {
@@ -601,6 +604,7 @@ class TextServerAdvanced : public TextServerExtension {
extra_spacing[SPACING_BOTTOM] = p_fb->_font_get_spacing(p_font, SPACING_BOTTOM);
extra_spacing[SPACING_SPACE] = p_fb->_font_get_spacing(p_font, SPACING_SPACE);
extra_spacing[SPACING_GLYPH] = p_fb->_font_get_spacing(p_font, SPACING_GLYPH);
+ baseline_offset = p_fb->_font_get_baseline_offset(p_font);
}
};
@@ -633,7 +637,7 @@ class TextServerAdvanced : public TextServerExtension {
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_BOTTOM], hash);
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_SPACE], hash);
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_GLYPH], hash);
-
+ hash = hash_murmur3_one_double(p_a.baseline_offset, hash);
return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash));
}
};
@@ -781,6 +785,9 @@ public:
MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t);
MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType);
+ MODBIND2(font_set_baseline_offset, const RID &, float);
+ MODBIND1RC(float, font_get_baseline_offset, const RID &);
+
MODBIND2(font_set_transform, const RID &, const Transform2D &);
MODBIND1RC(Transform2D, font_get_transform, const RID &);
diff --git a/modules/text_server_adv/thorvg_bounds_iterator.cpp b/modules/text_server_adv/thorvg_bounds_iterator.cpp
index 807f356b83..d273eef97f 100644
--- a/modules/text_server_adv/thorvg_bounds_iterator.cpp
+++ b/modules/text_server_adv/thorvg_bounds_iterator.cpp
@@ -35,7 +35,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/typedefs.h"
diff --git a/modules/text_server_adv/thorvg_bounds_iterator.h b/modules/text_server_adv/thorvg_bounds_iterator.h
index a44cbb99a7..afa2c13764 100644
--- a/modules/text_server_adv/thorvg_bounds_iterator.h
+++ b/modules/text_server_adv/thorvg_bounds_iterator.h
@@ -39,7 +39,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/typedefs.h"
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp
index 828f8d7d56..136ccf3aaf 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp
@@ -38,7 +38,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/error/error_macros.h"
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h
index 034fffb5e6..ce048674fd 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.h
+++ b/modules/text_server_adv/thorvg_svg_in_ot.h
@@ -40,7 +40,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/os/mutex.h"
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index 0d2fbd97fd..0efced0bfc 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -69,6 +69,8 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/renderer/tvgShape.cpp",
"src/renderer/tvgSwCanvas.cpp",
"src/renderer/tvgTaskScheduler.cpp",
+ "src/renderer/tvgText.cpp",
+ # "src/renderer/tvgWgCanvas.cpp",
# renderer sw_engine
"src/renderer/sw_engine/tvgSwFill.cpp",
"src/renderer/sw_engine/tvgSwImage.cpp",
diff --git a/modules/text_server_fb/register_types.h b/modules/text_server_fb/register_types.h
index 97bc06a8f7..0933ea83c5 100644
--- a/modules/text_server_fb/register_types.h
+++ b/modules/text_server_fb/register_types.h
@@ -34,7 +34,7 @@
#ifdef GDEXTENSION
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
#include "modules/register_module_types.h"
#endif
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 8fc7694aa4..d9689f0441 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -44,7 +44,7 @@ using namespace godot;
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get_setting_with_override(m_var)
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/config/project_settings.h"
@@ -95,7 +95,7 @@ bool TextServerFallback::_has_feature(Feature p_feature) const {
String TextServerFallback::_get_name() const {
#ifdef GDEXTENSION
return "Fallback (GDExtension)";
-#else
+#elif defined(GODOT_MODULE)
return "Fallback (Built-in)";
#endif
}
@@ -1403,6 +1403,37 @@ int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType
}
}
+void TextServerFallback::_font_set_baseline_offset(const RID &p_font_rid, float p_baseline_offset) {
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->baseline_offset != p_baseline_offset) {
+ fdv->baseline_offset = p_baseline_offset;
+ }
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->baseline_offset != p_baseline_offset) {
+ _font_clear_cache(fd);
+ fd->baseline_offset = p_baseline_offset;
+ }
+ }
+}
+
+float TextServerFallback::_font_get_baseline_offset(const RID &p_font_rid) const {
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->baseline_offset;
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0.0);
+
+ MutexLock lock(fd->mutex);
+ return fd->baseline_offset;
+ }
+}
+
void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -3654,7 +3685,7 @@ RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
-#else
+#elif defined(GODOT_MODULE)
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, p_fdef, this);
@@ -4105,12 +4136,12 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x;
gl.x_off = 0;
- gl.y_off = 0;
+ gl.y_off = _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_TOP));
sd->descent = MAX(sd->descent, _font_get_descent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_BOTTOM));
} else {
gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y;
- gl.x_off = -Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5);
+ gl.x_off = -Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5) + _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size));
gl.y_off = _font_get_ascent(gl.font_rid, gl.font_size);
sd->ascent = MAX(sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
sd->descent = MAX(sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 68e0cbff6c..74534174b1 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -76,7 +76,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/extension/ext_wrappers.gen.inc"
@@ -248,6 +248,7 @@ class TextServerFallback : public TextServerExtension {
struct FontFallbackLinkedVariation {
RID base_font;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
};
struct FontFallback {
@@ -275,6 +276,7 @@ class TextServerFallback : public TextServerExtension {
int weight = 400;
int stretch = 100;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
@@ -490,9 +492,10 @@ class TextServerFallback : public TextServerExtension {
double embolden = 0.0;
Transform2D transform;
int extra_spacing[4] = { 0, 0, 0, 0 };
+ float baseline_offset = 0.0;
bool operator==(const SystemFontKey &p_b) const {
- return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]);
+ return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset);
}
SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) {
@@ -517,6 +520,7 @@ class TextServerFallback : public TextServerExtension {
extra_spacing[SPACING_BOTTOM] = p_fb->_font_get_spacing(p_font, SPACING_BOTTOM);
extra_spacing[SPACING_SPACE] = p_fb->_font_get_spacing(p_font, SPACING_SPACE);
extra_spacing[SPACING_GLYPH] = p_fb->_font_get_spacing(p_font, SPACING_GLYPH);
+ baseline_offset = p_fb->_font_get_baseline_offset(p_font);
}
};
@@ -549,6 +553,7 @@ class TextServerFallback : public TextServerExtension {
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_BOTTOM], hash);
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_SPACE], hash);
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_GLYPH], hash);
+ hash = hash_murmur3_one_double(p_a.baseline_offset, hash);
return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash));
}
};
@@ -648,6 +653,9 @@ public:
MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t);
MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType);
+ MODBIND2(font_set_baseline_offset, const RID &, float);
+ MODBIND1RC(float, font_get_baseline_offset, const RID &);
+
MODBIND2(font_set_transform, const RID &, const Transform2D &);
MODBIND1RC(Transform2D, font_get_transform, const RID &);
diff --git a/modules/text_server_fb/thorvg_bounds_iterator.cpp b/modules/text_server_fb/thorvg_bounds_iterator.cpp
index 807f356b83..d273eef97f 100644
--- a/modules/text_server_fb/thorvg_bounds_iterator.cpp
+++ b/modules/text_server_fb/thorvg_bounds_iterator.cpp
@@ -35,7 +35,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/typedefs.h"
diff --git a/modules/text_server_fb/thorvg_bounds_iterator.h b/modules/text_server_fb/thorvg_bounds_iterator.h
index a44cbb99a7..afa2c13764 100644
--- a/modules/text_server_fb/thorvg_bounds_iterator.h
+++ b/modules/text_server_fb/thorvg_bounds_iterator.h
@@ -39,7 +39,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/typedefs.h"
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp
index 7c8fedabc8..1ad33a88d4 100644
--- a/modules/text_server_fb/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp
@@ -38,7 +38,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/error/error_macros.h"
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.h b/modules/text_server_fb/thorvg_svg_in_ot.h
index 034fffb5e6..ce048674fd 100644
--- a/modules/text_server_fb/thorvg_svg_in_ot.h
+++ b/modules/text_server_fb/thorvg_svg_in_ot.h
@@ -40,7 +40,7 @@
using namespace godot;
-#else
+#elif defined(GODOT_MODULE)
// Headers for building as built-in module.
#include "core/os/mutex.h"
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index aef4f394b2..2812f37eb2 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -265,7 +265,7 @@ void UPNP::clear_devices() {
}
Ref<UPNPDevice> UPNP::get_gateway() const {
- ERR_FAIL_COND_V_MSG(devices.size() < 1, nullptr, "Couldn't find any UPNPDevices.");
+ ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices.");
for (int i = 0; i < devices.size(); i++) {
Ref<UPNPDevice> dev = get_device(i);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 7ec0b697bf..e6003f35df 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -46,8 +46,9 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
int todo = p_frames;
int beat_length_frames = -1;
- bool beat_loop = vorbis_stream->has_loop();
- if (beat_loop && vorbis_stream->get_bpm() > 0 && vorbis_stream->get_beat_count() > 0) {
+ bool use_loop = looping_override ? looping : vorbis_stream->loop;
+
+ if (use_loop && vorbis_stream->get_bpm() > 0 && vorbis_stream->get_beat_count() > 0) {
beat_length_frames = vorbis_stream->get_beat_count() * vorbis_data->get_sampling_rate() * 60 / vorbis_stream->get_bpm();
}
@@ -99,7 +100,7 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
} else
**/
- if (beat_loop && beat_length_frames <= (int)frames_mixed) {
+ if (use_loop && beat_length_frames <= (int)frames_mixed) {
// End of file when doing beat-based looping. <= used instead of == because importer editing
if (!have_packets_left && !have_samples_left) {
//Nothing remaining, so do nothing.
@@ -125,7 +126,7 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
if (!have_packets_left && !have_samples_left) {
// Actual end of file!
bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0;
- if (vorbis_stream->loop && is_not_empty) {
+ if (use_loop && is_not_empty) {
//loop
seek(vorbis_stream->loop_offset);
@@ -257,6 +258,25 @@ void AudioStreamPlaybackOggVorbis::tag_used_streams() {
vorbis_stream->tag_used(get_playback_position());
}
+void AudioStreamPlaybackOggVorbis::set_parameter(const StringName &p_name, const Variant &p_value) {
+ if (p_name == SNAME("looping")) {
+ if (p_value == Variant()) {
+ looping_override = false;
+ looping = false;
+ } else {
+ looping_override = true;
+ looping = p_value;
+ }
+ }
+}
+
+Variant AudioStreamPlaybackOggVorbis::get_parameter(const StringName &p_name) const {
+ if (looping_override && p_name == SNAME("looping")) {
+ return looping;
+ }
+ return Variant();
+}
+
void AudioStreamPlaybackOggVorbis::seek(double p_time) {
ERR_FAIL_COND(!ready);
ERR_FAIL_COND(vorbis_stream.is_null());
@@ -493,6 +513,10 @@ bool AudioStreamOggVorbis::is_monophonic() const {
return false;
}
+void AudioStreamOggVorbis::get_parameter_list(List<Parameter> *r_parameters) {
+ r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant()));
+}
+
void AudioStreamOggVorbis::_bind_methods() {
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer);
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h
index 6abaeaaebd..64a7815b57 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.h
+++ b/modules/vorbis/audio_stream_ogg_vorbis.h
@@ -44,6 +44,8 @@ class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled {
uint32_t frames_mixed = 0;
bool active = false;
+ bool looping_override = false;
+ bool looping = false;
int loops = 0;
enum {
@@ -95,6 +97,9 @@ public:
virtual void tag_used_streams() override;
+ virtual void set_parameter(const StringName &p_name, const Variant &p_value) override;
+ virtual Variant get_parameter(const StringName &p_name) const override;
+
AudioStreamPlaybackOggVorbis() {}
~AudioStreamPlaybackOggVorbis();
};
@@ -152,6 +157,8 @@ public:
virtual bool is_monophonic() const override;
+ virtual void get_parameter_list(List<Parameter> *r_parameters) override;
+
AudioStreamOggVorbis();
virtual ~AudioStreamOggVorbis();
};
diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp
index 791b6d9ec9..dc6833e8c3 100644
--- a/modules/websocket/remote_debugger_peer_websocket.cpp
+++ b/modules/websocket/remote_debugger_peer_websocket.cpp
@@ -91,7 +91,7 @@ bool RemoteDebuggerPeerWebSocket::has_message() {
}
Array RemoteDebuggerPeerWebSocket::get_message() {
- ERR_FAIL_COND_V(in_queue.size() < 1, Array());
+ ERR_FAIL_COND_V(in_queue.is_empty(), Array());
Array msg = in_queue[0];
in_queue.pop_front();
return msg;
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 9e706dbeef..332cf93d36 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -124,7 +124,7 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff
current_packet.data = nullptr;
}
- ERR_FAIL_COND_V(incoming_packets.size() == 0, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), ERR_UNAVAILABLE);
current_packet = incoming_packets.front()->get();
incoming_packets.pop_front();
@@ -164,7 +164,7 @@ void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) {
}
int WebSocketMultiplayerPeer::get_packet_peer() const {
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
return incoming_packets.front()->get().source;
}
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 828476edfb..5415423fb8 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -675,6 +675,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
event->set_index(touch_index);
event->set_position(position);
event->set_relative(delta);
+ event->set_relative_screen_position(delta);
Input::get_singleton()->parse_input_event(event);
}
}
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index e67c65d4d1..e96d9da7a9 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -88,7 +88,7 @@ Error ZIPPacker::start_file(const String &p_path) {
Z_DEFAULT_STRATEGY,
nullptr,
0,
- 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions.
+ 0, // "version made by", indicates the compatibility of the file attribute information (the `external_fa` field above).
1 << 11); // Bit 11 is the language encoding flag. When set, filename and comment fields must be encoded using UTF-8.
return err == ZIP_OK ? OK : FAILED;
}