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/csg/doc_classes/CSGPolygon3D.xml2
-rw-r--r--modules/csg/editor/csg_gizmos.cpp2
-rw-r--r--modules/dds/texture_loader_dds.cpp493
-rw-r--r--modules/etcpak/SCsub1
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp76
-rw-r--r--modules/etcpak/image_compress_etcpak.h4
-rw-r--r--modules/gdscript/README.md139
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml4
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp4
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp5
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp124
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h1
-rw-r--r--modules/gdscript/gdscript.cpp246
-rw-r--r--modules/gdscript/gdscript.h39
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp169
-rw-r--r--modules/gdscript/gdscript_analyzer.h3
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp20
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_cache.cpp25
-rw-r--r--modules/gdscript/gdscript_compiler.cpp58
-rw-r--r--modules/gdscript/gdscript_compiler.h20
-rw-r--r--modules/gdscript/gdscript_editor.cpp318
-rw-r--r--modules/gdscript/gdscript_function.h63
-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.cpp149
-rw-r--r--modules/gdscript/gdscript_parser.h66
-rw-r--r--modules/gdscript/gdscript_tokenizer.h6
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp7
-rw-r--r--modules/gdscript/gdscript_vm.cpp161
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp22
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp13
-rw-r--r--modules/gdscript/tests/README.md41
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp13
-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/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/test_completion.h203
-rw-r--r--modules/gdscript/tests/test_lsp.h18
-rw-r--r--modules/glslang/config.py4
-rw-r--r--modules/glslang/register_types.cpp7
-rw-r--r--modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml6
-rw-r--r--modules/gltf/doc_classes/GLTFAnimation.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFBufferView.xml19
-rw-r--r--modules/gltf/doc_classes/GLTFCamera.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml3
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml5
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFLight.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFMesh.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml20
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsShape.xml8
-rw-r--r--modules/gltf/doc_classes/GLTFSkeleton.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFSkin.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFSpecGloss.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml9
-rw-r--r--modules/gltf/doc_classes/GLTFTexture.xml1
-rw-r--r--modules/gltf/doc_classes/GLTFTextureSampler.xml1
-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.cpp180
-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.cpp313
-rw-r--r--modules/gltf/gltf_document.h1
-rw-r--r--modules/gltf/gltf_state.cpp28
-rw-r--r--modules/gltf/gltf_state.h1
-rw-r--r--modules/gltf/register_types.cpp51
-rw-r--r--modules/gltf/structures/gltf_buffer_view.cpp14
-rw-r--r--modules/gltf/structures/gltf_buffer_view.h3
-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/gridmap/grid_map.h1
-rw-r--r--modules/jpg/image_loader_jpegd.cpp19
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp34
-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/register_types.cpp5
-rw-r--r--modules/minimp3/resource_importer_mp3.cpp2
-rw-r--r--modules/mono/csharp_script.cpp79
-rw-r--r--modules/mono/csharp_script.h5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets4
-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/CSharpSourceGeneratorVerifier.cs82
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Constants.cs23
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Extensions.cs12
-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.csproj43
-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/ScriptMethodsGeneratorTests.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs55
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs60
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSignalsGeneratorTests.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/.editorconfig5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs66
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs58
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Bar_ScriptPath.generated.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs54
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs816
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs139
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs933
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs147
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Foo_ScriptPath.generated.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs61
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs94
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs52
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptProperties.generated.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptSerialization.generated.cs21
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs62
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptPath.generated.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs62
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllReadOnly.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllWriteOnly.cs7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Bar.cs14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedFields.cs102
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties.cs186
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Foo.cs10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs18
-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/Methods.cs26
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MixedReadOnlyWriteOnly.cs12
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MoreExportedFields.cs8
-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.Tests/TestData/Sources/ScriptBoilerplate.cs33
-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.csproj4
-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/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/Build/BuildProblemsView.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs38
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorOutOfSyncWarning.cs37
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs45
-rw-r--r--modules/mono/editor/bindings_generator.cpp30
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp6
-rw-r--r--modules/mono/glue/GodotSharp/.editorconfig3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs13
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs25
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs25
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs25
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj2
-rw-r--r--modules/mono/glue/runtime_interop.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono.h2
-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/scene_cache_interface.cpp164
-rw-r--r--modules/multiplayer/scene_cache_interface.h27
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp5
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp4
-rw-r--r--modules/multiplayer/scene_replication_interface.h4
-rw-r--r--modules/navigation/godot_navigation_server.cpp158
-rw-r--r--modules/navigation/godot_navigation_server.h23
-rw-r--r--modules/navigation/godot_navigation_server_2d.cpp74
-rw-r--r--modules/navigation/godot_navigation_server_2d.h23
-rw-r--r--modules/navigation/nav_agent.cpp4
-rw-r--r--modules/navigation/nav_map.cpp83
-rw-r--r--modules/navigation/nav_map.h2
-rw-r--r--modules/navigation/nav_mesh_generator_2d.cpp14
-rw-r--r--modules/navigation/nav_region.cpp140
-rw-r--r--modules/navigation/nav_region.h6
-rw-r--r--modules/navigation/nav_utils.h4
-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/ogg/ogg_packet_sequence.cpp4
-rw-r--r--modules/openxr/SCsub32
-rw-r--r--modules/openxr/action_map/SCsub10
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp1
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile_metadata.cpp3
-rw-r--r--modules/openxr/doc_classes/OpenXRHand.xml31
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml24
-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_fb_update_swapchain_extension.cpp2
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp57
-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)2
-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)2
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.h (renamed from modules/openxr/extensions/openxr_vulkan_extension.h)10
-rw-r--r--modules/openxr/openxr_api.cpp151
-rw-r--r--modules/openxr/openxr_api.h16
-rw-r--r--modules/openxr/openxr_interface.cpp101
-rw-r--r--modules/openxr/openxr_interface.h11
-rw-r--r--modules/openxr/register_types.cpp6
-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/squish/image_decompress_squish.cpp46
-rw-r--r--modules/svg/SCsub2
-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/gdextension_build/methods.py4
-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.cpp445
-rw-r--r--modules/text_server_adv/text_server_adv.h18
-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/gdextension_build/methods.py4
-rw-r--r--modules/text_server_fb/register_types.h2
-rw-r--r--modules/text_server_fb/text_server_fb.cpp437
-rw-r--r--modules/text_server_fb/text_server_fb.h17
-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/tinyexr/image_loader_tinyexr.cpp2
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp38
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.h7
-rw-r--r--modules/vorbis/doc_classes/AudioStreamOggVorbis.xml1
-rw-r--r--modules/vorbis/register_types.cpp5
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp2
-rw-r--r--modules/webp/webp_common.cpp4
-rw-r--r--modules/webrtc/doc_classes/WebRTCPeerConnection.xml2
-rw-r--r--modules/webxr/webxr_interface_js.cpp38
-rw-r--r--modules/zip/zip_packer.cpp19
327 files changed, 11417 insertions, 2292 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 9c178997c5..1de76c60b5 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;
@@ -2226,7 +2226,7 @@ void CSGPolygon3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path3D"), "set_path_node", "get_path_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_interval_type", PROPERTY_HINT_ENUM, "Distance,Subdivide"), "set_path_interval_type", "get_path_interval_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.01,1.0,0.01,exp,or_greater"), "set_path_interval", "get_path_interval");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1,exp"), "set_path_simplify_angle", "get_path_simplify_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1"), "set_path_simplify_angle", "get_path_simplify_angle");
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u");
diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml
index 338adc9b52..5d35c04e25 100644
--- a/modules/csg/doc_classes/CSGPolygon3D.xml
+++ b/modules/csg/doc_classes/CSGPolygon3D.xml
@@ -39,7 +39,7 @@
When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon].
</member>
<member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation">
- When [member mode] is [constant MODE_PATH], the [enum PathRotation] method used to rotate the [member polygon] as it is extruded.
+ When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded.
</member>
<member name="path_simplify_angle" type="float" setter="set_path_simplify_angle" getter="get_path_simplify_angle">
When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count.
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index ebf0f5a91f..ea7b6d225e 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -44,7 +44,7 @@
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
helper.instantiate();
- Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
+ Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
create_material("shape_union_material", gizmo_color);
create_material("shape_union_solid_material", gizmo_color);
gizmo_color.invert();
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index 481bb46c24..b2de6b656e 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -44,24 +44,79 @@ enum {
DDSD_MIPMAPCOUNT = 0x00020000,
DDPF_FOURCC = 0x00000004,
DDPF_ALPHAPIXELS = 0x00000001,
- DDPF_INDEXED = 0x00000020,
- DDPF_RGB = 0x00000040,
+ DDPF_RGB = 0x00000040
};
+enum DDSFourCC {
+ DDFCC_DXT1 = PF_FOURCC("DXT1"),
+ DDFCC_DXT3 = PF_FOURCC("DXT3"),
+ DDFCC_DXT5 = PF_FOURCC("DXT5"),
+ DDFCC_ATI1 = PF_FOURCC("ATI1"),
+ DDFCC_BC4U = PF_FOURCC("BC4U"),
+ DDFCC_ATI2 = PF_FOURCC("ATI2"),
+ DDFCC_BC5U = PF_FOURCC("BC5U"),
+ DDFCC_A2XY = PF_FOURCC("A2XY"),
+ DDFCC_DX10 = PF_FOURCC("DX10"),
+ DDFCC_R16F = 111,
+ DDFCC_RG16F = 112,
+ DDFCC_RGBA16F = 113,
+ DDFCC_R32F = 114,
+ DDFCC_RG32F = 115,
+ DDFCC_RGBA32F = 116
+};
+
+// Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
+enum DXGIFormat {
+ DXGI_R32G32B32A32_FLOAT = 2,
+ DXGI_R16G16B16A16_FLOAT = 10,
+ DXGI_R32G32_FLOAT = 16,
+ DXGI_R10G10B10A2_UNORM = 24,
+ DXGI_R8G8B8A8_UNORM = 28,
+ DXGI_R16G16_FLOAT = 34,
+ DXGI_R32_FLOAT = 41,
+ DXGI_R16_FLOAT = 54,
+ DXGI_R9G9B9E5 = 67,
+ DXGI_BC1_UNORM = 71,
+ DXGI_BC2_UNORM = 74,
+ DXGI_BC3_UNORM = 77,
+ DXGI_BC4_UNORM = 80,
+ DXGI_BC5_UNORM = 83,
+ DXGI_B5G6R5_UNORM = 85,
+ DXGI_B5G5R5A1_UNORM = 86,
+ DXGI_B8G8R8A8_UNORM = 87,
+ DXGI_BC6H_UF16 = 95,
+ DXGI_BC6H_SF16 = 96,
+ DXGI_BC7_UNORM = 98,
+ DXGI_B4G4R4A4_UNORM = 115
+};
+
+// The legacy bitmasked format names here represent the actual data layout in the files,
+// while their official names are flipped (e.g. RGBA8 layout is officially called ABGR8).
enum DDSFormat {
DDS_DXT1,
DDS_DXT3,
DDS_DXT5,
DDS_ATI1,
DDS_ATI2,
- DDS_A2XY,
+ DDS_BC6U,
+ DDS_BC6S,
+ DDS_BC7U,
+ DDS_R16F,
+ DDS_RG16F,
+ DDS_RGBA16F,
+ DDS_R32F,
+ DDS_RG32F,
+ DDS_RGBA32F,
+ DDS_RGB9E5,
DDS_BGRA8,
DDS_BGR8,
- DDS_RGBA8, //flipped in dds
- DDS_RGB8, //flipped in dds
+ DDS_RGBA8,
+ DDS_RGB8,
DDS_BGR5A1,
DDS_BGR565,
DDS_BGR10A2,
+ DDS_RGB10A2,
+ DDS_BGRA4,
DDS_LUMINANCE,
DDS_LUMINANCE_ALPHA,
DDS_MAX
@@ -70,30 +125,112 @@ enum DDSFormat {
struct DDSFormatInfo {
const char *name = nullptr;
bool compressed = false;
- bool palette = false;
uint32_t divisor = 0;
uint32_t block_size = 0;
Image::Format format = Image::Format::FORMAT_BPTC_RGBA;
};
static const DDSFormatInfo dds_format_info[DDS_MAX] = {
- { "DXT1/BC1", true, false, 4, 8, Image::FORMAT_DXT1 },
- { "DXT3/BC2", true, false, 4, 16, Image::FORMAT_DXT3 },
- { "DXT5/BC3", true, false, 4, 16, Image::FORMAT_DXT5 },
- { "ATI1/BC4", true, false, 4, 8, Image::FORMAT_RGTC_R },
- { "ATI2/3DC/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
- { "A2XY/DXN/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
- { "BGRA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "BGR8", false, false, 1, 3, Image::FORMAT_RGB8 },
- { "RGBA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "RGB8", false, false, 1, 3, Image::FORMAT_RGB8 },
- { "BGR5A1", false, false, 1, 2, Image::FORMAT_RGBA8 },
- { "BGR565", false, false, 1, 2, Image::FORMAT_RGB8 },
- { "BGR10A2", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "GRAYSCALE", false, false, 1, 1, Image::FORMAT_L8 },
- { "GRAYSCALE_ALPHA", false, false, 1, 2, Image::FORMAT_LA8 }
+ { "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 },
+ { "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
+ { "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
+ { "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R },
+ { "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG },
+ { "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
+ { "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF },
+ { "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA },
+ { "R16F", false, 1, 2, Image::FORMAT_RH },
+ { "RG16F", false, 1, 4, Image::FORMAT_RGH },
+ { "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH },
+ { "R32F", false, 1, 4, Image::FORMAT_RF },
+ { "RG32F", false, 1, 8, Image::FORMAT_RGF },
+ { "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF },
+ { "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 },
+ { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
+ { "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
+ { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
+ { "RGB8", false, 1, 3, Image::FORMAT_RGB8 },
+ { "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 },
+ { "BGR565", false, 1, 2, Image::FORMAT_RGB8 },
+ { "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 },
+ { "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 },
+ { "BGRA4", false, 1, 2, Image::FORMAT_RGBA8 },
+ { "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 },
+ { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }
};
+static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
+ switch (p_dxgi_format) {
+ case DXGI_R32G32B32A32_FLOAT: {
+ return DDS_RGBA32F;
+ }
+ case DXGI_R16G16B16A16_FLOAT: {
+ return DDS_RGBA16F;
+ }
+ case DXGI_R32G32_FLOAT: {
+ return DDS_RG32F;
+ }
+ case DXGI_R10G10B10A2_UNORM: {
+ return DDS_RGB10A2;
+ }
+ case DXGI_R8G8B8A8_UNORM: {
+ return DDS_RGBA8;
+ }
+ case DXGI_R16G16_FLOAT: {
+ return DDS_RG16F;
+ }
+ case DXGI_R32_FLOAT: {
+ return DDS_R32F;
+ }
+ case DXGI_R16_FLOAT: {
+ return DDS_R16F;
+ }
+ case DXGI_R9G9B9E5: {
+ return DDS_RGB9E5;
+ }
+ case DXGI_BC1_UNORM: {
+ return DDS_DXT1;
+ }
+ case DXGI_BC2_UNORM: {
+ return DDS_DXT3;
+ }
+ case DXGI_BC3_UNORM: {
+ return DDS_DXT5;
+ }
+ case DXGI_BC4_UNORM: {
+ return DDS_ATI1;
+ }
+ case DXGI_BC5_UNORM: {
+ return DDS_ATI2;
+ }
+ case DXGI_B5G6R5_UNORM: {
+ return DDS_BGR565;
+ }
+ case DXGI_B5G5R5A1_UNORM: {
+ return DDS_BGR5A1;
+ }
+ case DXGI_B8G8R8A8_UNORM: {
+ return DDS_BGRA8;
+ }
+ case DXGI_BC6H_UF16: {
+ return DDS_BC6U;
+ }
+ case DXGI_BC6H_SF16: {
+ return DDS_BC6S;
+ }
+ case DXGI_BC7_UNORM: {
+ return DDS_BC7U;
+ }
+ case DXGI_B4G4R4A4_UNORM: {
+ return DDS_BGRA4;
+ }
+
+ default: {
+ return DDS_MAX;
+ }
+ }
+}
+
Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
@@ -121,15 +258,14 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
/* uint32_t depth = */ f->get_32();
uint32_t mipmaps = f->get_32();
- //skip 11
+ // Skip reserved.
for (int i = 0; i < 11; i++) {
f->get_32();
}
- //validate
-
+ // Validate.
// We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
- // but non-mandatory when reading (as some writers don't set them)...
+ // but non-mandatory when reading (as some writers don't set them).
if (magic != DDS_MAGIC || hsize != 124) {
ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
}
@@ -145,65 +281,112 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
/* uint32_t caps_1 = */ f->get_32();
/* uint32_t caps_2 = */ f->get_32();
- /* uint32_t caps_ddsx = */ f->get_32();
+ /* uint32_t caps_3 = */ f->get_32();
+ /* uint32_t caps_4 = */ f->get_32();
- //reserved skip
- f->get_32();
+ // Skip reserved.
f->get_32();
- /*
- print_line("DDS width: "+itos(width));
- print_line("DDS height: "+itos(height));
- print_line("DDS mipmaps: "+itos(mipmaps));
+ if (f->get_position() < 128) {
+ f->seek(128);
+ }
- printf("fourcc: %x fflags: %x, rgbbits: %x, fsize: %x\n",format_fourcc,format_flags,format_rgb_bits,format_size);
- printf("rmask: %x gmask: %x, bmask: %x, amask: %x\n",format_red_mask,format_green_mask,format_blue_mask,format_alpha_mask);
- */
+ DDSFormat dds_format = DDS_MAX;
- //must avoid this later
- while (f->get_position() < 128) {
- f->get_8();
- }
+ if (format_flags & DDPF_FOURCC) {
+ // FourCC formats.
+ switch (format_fourcc) {
+ case DDFCC_DXT1: {
+ dds_format = DDS_DXT1;
+ } break;
+ case DDFCC_DXT3: {
+ dds_format = DDS_DXT3;
+ } break;
+ case DDFCC_DXT5: {
+ dds_format = DDS_DXT5;
+ } break;
+ case DDFCC_ATI1:
+ case DDFCC_BC4U: {
+ dds_format = DDS_ATI1;
+ } break;
+ case DDFCC_ATI2:
+ case DDFCC_BC5U:
+ case DDFCC_A2XY: {
+ dds_format = DDS_ATI2;
+ } break;
+ case DDFCC_R16F: {
+ dds_format = DDS_R16F;
+ } break;
+ case DDFCC_RG16F: {
+ dds_format = DDS_RG16F;
+ } break;
+ case DDFCC_RGBA16F: {
+ dds_format = DDS_RGBA16F;
+ } break;
+ case DDFCC_R32F: {
+ dds_format = DDS_R32F;
+ } break;
+ case DDFCC_RG32F: {
+ dds_format = DDS_RG32F;
+ } break;
+ case DDFCC_RGBA32F: {
+ dds_format = DDS_RGBA32F;
+ } break;
+ case DDFCC_DX10: {
+ uint32_t dxgi_format = f->get_32();
+ /* uint32_t dimension = */ f->get_32();
+ /* uint32_t misc_flags_1 = */ f->get_32();
+ /* uint32_t array_size = */ f->get_32();
+ /* uint32_t misc_flags_2 = */ f->get_32();
+
+ dds_format = dxgi_to_dds_format(dxgi_format);
+ } break;
+
+ default: {
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported FourCC in DDS '" + p_path + "'.");
+ }
+ }
+
+ } else if (format_flags & DDPF_RGB) {
+ // Channel-bitmasked formats.
+ if (format_flags & DDPF_ALPHAPIXELS) {
+ // With alpha.
+ if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_BGRA8;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_RGBA8;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
+ dds_format = DDS_BGR5A1;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
+ dds_format = DDS_BGR10A2;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000) {
+ dds_format = DDS_RGB10A2;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) {
+ dds_format = DDS_BGRA4;
+ }
+
+ } else {
+ // Without alpha.
+ if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
+ dds_format = DDS_BGR8;
+ } else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
+ dds_format = DDS_RGB8;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
+ dds_format = DDS_BGR565;
+ }
+ }
- DDSFormat dds_format;
-
- if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT1")) {
- dds_format = DDS_DXT1;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT3")) {
- dds_format = DDS_DXT3;
-
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT5")) {
- dds_format = DDS_DXT5;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI1")) {
- dds_format = DDS_ATI1;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI2")) {
- dds_format = DDS_ATI2;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("A2XY")) {
- dds_format = DDS_A2XY;
-
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
- dds_format = DDS_BGRA8;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
- dds_format = DDS_BGR8;
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
- dds_format = DDS_RGBA8;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
- dds_format = DDS_RGB8;
-
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
- dds_format = DDS_BGR5A1;
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
- dds_format = DDS_BGR10A2;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
- dds_format = DDS_BGR565;
- } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) {
- dds_format = DDS_LUMINANCE;
- } else if ((format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
- dds_format = DDS_LUMINANCE_ALPHA;
- } else if (format_flags & DDPF_INDEXED && format_rgb_bits == 8) {
- dds_format = DDS_BGR565;
} else {
- //printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask);
+ // Other formats.
+ if (format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
+ dds_format = DDS_LUMINANCE_ALPHA;
+ } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) {
+ dds_format = DDS_LUMINANCE;
+ }
+ }
+
+ // No format detected, error.
+ if (dds_format == DDS_MAX) {
ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
}
@@ -218,17 +401,20 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
uint32_t h = height;
if (info.compressed) {
- //compressed bc
-
+ // BC compressed.
uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- ERR_FAIL_COND_V(size != pitch, Ref<Resource>());
- ERR_FAIL_COND_V(!(flags & DDSD_LINEARSIZE), Ref<Resource>());
+
+ if (flags & DDSD_LINEARSIZE) {
+ ERR_FAIL_COND_V_MSG(size != pitch, Ref<Resource>(), "DDS header flags specify that a linear size of the top-level image is present, but the specified size does not match the expected value.");
+ } else {
+ ERR_FAIL_COND_V_MSG(pitch != 0, Ref<Resource>(), "DDS header flags specify that no linear size will given for the top-level image, but a non-zero linear size value is present in the header.");
+ }
for (uint32_t i = 1; i < mipmaps; i++) {
w = MAX(1u, w >> 1);
h = MAX(1u, h >> 1);
+
uint32_t bsize = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- //printf("%i x %i - block: %i\n",w,h,bsize);
size += bsize;
}
@@ -236,50 +422,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
uint8_t *wb = src_data.ptrw();
f->get_buffer(wb, size);
- } else if (info.palette) {
- //indexed
- ERR_FAIL_COND_V(!(flags & DDSD_PITCH), Ref<Resource>());
- ERR_FAIL_COND_V(format_rgb_bits != 8, Ref<Resource>());
-
- uint32_t size = pitch * height;
- ERR_FAIL_COND_V(size != width * height * info.block_size, Ref<Resource>());
-
- uint8_t palette[256 * 4];
- f->get_buffer(palette, 256 * 4);
-
- int colsize = 3;
- for (int i = 0; i < 256; i++) {
- if (palette[i * 4 + 3] < 255) {
- colsize = 4;
- }
- }
-
- int w2 = width;
- int h2 = height;
-
- for (uint32_t i = 1; i < mipmaps; i++) {
- w2 = (w2 + 1) >> 1;
- h2 = (h2 + 1) >> 1;
- size += w2 * h2 * info.block_size;
- }
-
- src_data.resize(size + 256 * colsize);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- for (int i = 0; i < 256; i++) {
- int dst_ofs = size + i * colsize;
- int src_ofs = i * 4;
- wb[dst_ofs + 0] = palette[src_ofs + 2];
- wb[dst_ofs + 1] = palette[src_ofs + 1];
- wb[dst_ofs + 2] = palette[src_ofs + 0];
- if (colsize == 4) {
- wb[dst_ofs + 3] = palette[src_ofs + 3];
- }
- }
} else {
- //uncompressed generic...
-
+ // Generic uncompressed.
uint32_t size = width * height * info.block_size;
for (uint32_t i = 1; i < mipmaps; i++) {
@@ -288,9 +432,10 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
size += w * h * info.block_size;
}
+ // Calculate the space these formats will take up after decoding.
if (dds_format == DDS_BGR565) {
size = size * 3 / 2;
- } else if (dds_format == DDS_BGR5A1) {
+ } else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) {
size = size * 2;
}
@@ -298,9 +443,10 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
uint8_t *wb = src_data.ptrw();
f->get_buffer(wb, size);
+ // Decode nonstandard formats.
switch (dds_format) {
case DDS_BGR5A1: {
- // TO RGBA
+ // To RGBA8.
int colcount = size / 4;
for (int i = colcount - 1; i >= 0; i--) {
@@ -311,13 +457,16 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
uint8_t b = wb[src_ofs] & 0x1F;
uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x3) << 3);
uint8_t r = (wb[src_ofs + 1] >> 2) & 0x1F;
+
wb[dst_ofs + 0] = r << 3;
wb[dst_ofs + 1] = g << 3;
wb[dst_ofs + 2] = b << 3;
wb[dst_ofs + 3] = a ? 255 : 0;
}
+
} break;
case DDS_BGR565: {
+ // To RGB8.
int colcount = size / 3;
for (int i = colcount - 1; i >= 0; i--) {
@@ -327,21 +476,67 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
uint8_t b = wb[src_ofs] & 0x1F;
uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x7) << 3);
uint8_t r = wb[src_ofs + 1] >> 3;
+
wb[dst_ofs + 0] = r << 3;
wb[dst_ofs + 1] = g << 2;
- wb[dst_ofs + 2] = b << 3; //b<<3;
+ wb[dst_ofs + 2] = b << 3;
}
} break;
- case DDS_BGR10A2: {
- // TO RGBA
+ case DDS_BGRA4: {
+ // To RGBA8.
int colcount = size / 4;
for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 4;
+
+ uint8_t b = wb[src_ofs] & 0x0F;
+ uint8_t g = wb[src_ofs] & 0xF0;
+ uint8_t r = wb[src_ofs + 1] & 0x0F;
+ uint8_t a = wb[src_ofs + 1] & 0xF0;
+
+ wb[dst_ofs] = (r << 4) | r;
+ wb[dst_ofs + 1] = g | (g >> 4);
+ wb[dst_ofs + 2] = (b << 4) | b;
+ wb[dst_ofs + 3] = a | (a >> 4);
+ }
+
+ } break;
+ case DDS_RGB10A2: {
+ // To RGBA8.
+ int colcount = size / 4;
+
+ for (int i = 0; i < colcount; i++) {
int ofs = i * 4;
uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
+ // This method follows the 'standard' way of decoding 10-bit dds files,
+ // which means the ones created with DirectXTex will be loaded incorrectly.
+ uint8_t a = (w32 & 0xc0000000) >> 24;
+ uint8_t r = (w32 & 0x3ff) >> 2;
+ uint8_t g = (w32 & 0xffc00) >> 12;
+ uint8_t b = (w32 & 0x3ff00000) >> 22;
+
+ wb[ofs + 0] = r;
+ wb[ofs + 1] = g;
+ wb[ofs + 2] = b;
+ wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
+ }
+
+ } break;
+ case DDS_BGR10A2: {
+ // To RGBA8.
+ int colcount = size / 4;
+
+ for (int i = 0; i < colcount; i++) {
+ int ofs = i * 4;
+
+ uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
+
+ // This method follows the 'standard' way of decoding 10-bit dds files,
+ // which means the ones created with DirectXTex will be loaded incorrectly.
uint8_t a = (w32 & 0xc0000000) >> 24;
uint8_t r = (w32 & 0x3ff00000) >> 22;
uint8_t g = (w32 & 0xffc00) >> 12;
@@ -350,10 +545,12 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
wb[ofs + 0] = r;
wb[ofs + 1] = g;
wb[ofs + 2] = b;
- wb[ofs + 3] = a == 0xc0 ? 255 : a; //0xc0 should be opaque
+ wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
}
+
} break;
case DDS_BGRA8: {
+ // To RGBA8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
@@ -362,44 +559,12 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
} break;
case DDS_BGR8: {
+ // To RGB8.
int colcount = size / 3;
for (int i = 0; i < colcount; i++) {
SWAP(wb[i * 3 + 0], wb[i * 3 + 2]);
}
- } break;
- case DDS_RGBA8: {
- /* do nothing either
- int colcount = size/4;
-
- for(int i=0;i<colcount;i++) {
- uint8_t r = wb[i*4+1];
- uint8_t g = wb[i*4+2];
- uint8_t b = wb[i*4+3];
- uint8_t a = wb[i*4+0];
-
- wb[i*4+0]=r;
- wb[i*4+1]=g;
- wb[i*4+2]=b;
- wb[i*4+3]=a;
- }
- */
- } break;
- case DDS_RGB8: {
- // do nothing
- /*
- int colcount = size/3;
-
- for(int i=0;i<colcount;i++) {
- SWAP( wb[i*3+0],wb[i*3+2] );
- }*/
- } break;
- case DDS_LUMINANCE: {
- // do nothing i guess?
-
- } break;
- case DDS_LUMINANCE_ALPHA: {
- // do nothing i guess?
} break;
diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub
index 2d3b69be75..3a4bff8e87 100644
--- a/modules/etcpak/SCsub
+++ b/modules/etcpak/SCsub
@@ -13,6 +13,7 @@ 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 14cce2686c..9c04f5b40e 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -35,17 +35,18 @@
#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:
@@ -62,9 +63,9 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
case Image::USED_CHANNELS_LA:
return EtcpakType::ETCPAK_TYPE_DXT5;
case Image::USED_CHANNELS_R:
- return EtcpakType::ETCPAK_TYPE_DXT5;
+ return EtcpakType::ETCPAK_TYPE_RGTC_R;
case Image::USED_CHANNELS_RG:
- return EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG;
+ return EtcpakType::ETCPAK_TYPE_RGTC_RG;
case Image::USED_CHANNELS_RGB:
return EtcpakType::ETCPAK_TYPE_DXT1;
case Image::USED_CHANNELS_RGBA:
@@ -113,6 +114,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();
@@ -127,6 +134,10 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
r_img->convert_rg_to_ra_rgba8();
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
target_format = Image::FORMAT_DXT5;
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
+ target_format = Image::FORMAT_RGTC_R;
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
+ target_format = Image::FORMAT_RGTC_RG;
} else {
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
}
@@ -219,18 +230,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 {
- 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:
+ CompressEtc2R8(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_RG:
+ CompressEtc2RG8(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:
+ CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w);
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_RGTC_RG:
+ CompressRgtcRG(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 ff267631a6..9d5343740b 100644
--- a/modules/etcpak/image_compress_etcpak.h
+++ b/modules/etcpak/image_compress_etcpak.h
@@ -38,9 +38,13 @@ 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,
+ ETCPAK_TYPE_RGTC_R,
+ ETCPAK_TYPE_RGTC_RG,
};
void _compress_etc1(Image *r_img);
diff --git a/modules/gdscript/README.md b/modules/gdscript/README.md
new file mode 100644
index 0000000000..30685e672c
--- /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 grey 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 3da6bcf10c..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
@@ -162,7 +162,7 @@
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
- Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload].
+ Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. This method is equivalent of using [method ResourceLoader.load] with [constant ResourceLoader.CACHE_MODE_REUSE].
[b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script.
[codeblock]
# Load a scene called "main" located in the root of the project directory and cache it in a variable.
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index c3979dd290..00179109a3 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -64,8 +64,8 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
r_type = p_is_return ? "void" : "null";
return;
}
- if (p_gdtype.builtin_type == Variant::ARRAY && p_gdtype.has_container_element_type()) {
- _doctype_from_gdtype(p_gdtype.get_container_element_type(), r_type, r_enum);
+ if (p_gdtype.builtin_type == Variant::ARRAY && p_gdtype.has_container_element_type(0)) {
+ _doctype_from_gdtype(p_gdtype.get_container_element_type(0), r_type, r_enum);
if (!r_enum.is_empty()) {
r_type = "int[]";
r_enum += "[]";
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 8dbd262b22..1f07def21c 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;
@@ -494,7 +495,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_function_arg_dicts = 0;
}
- if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') {
+ if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != ',' && str[j] != '.') {
expect_type = false;
}
@@ -790,7 +791,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);
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 9128f104b8..f55b00ebe1 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -80,7 +80,7 @@ bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptPa
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
- // There are 7 types of Member, but only class, function and variable can contain translatable strings.
+ // Other member types can't contain translatable strings.
switch (m.type) {
case GDScriptParser::ClassNode::Member::CLASS:
_traverse_class(m.m_class);
@@ -89,7 +89,11 @@ void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser
_traverse_function(m.function);
break;
case GDScriptParser::ClassNode::Member::VARIABLE:
- _read_variable(m.variable);
+ _assess_expression(m.variable->initializer);
+ if (m.variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
+ _traverse_function(m.variable->setter);
+ _traverse_function(m.variable->getter);
+ }
break;
default:
break;
@@ -98,11 +102,14 @@ void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser
}
void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) {
- _traverse_block(p_func->body);
-}
+ if (!p_func) {
+ return;
+ }
-void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) {
- _assess_expression(p_var->initializer);
+ for (int i = 0; i < p_func->parameters.size(); i++) {
+ _assess_expression(p_func->parameters[i]->initializer);
+ }
+ _traverse_block(p_func->body);
}
void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) {
@@ -114,53 +121,51 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
for (int i = 0; i < statements.size(); i++) {
const GDScriptParser::Node *statement = statements[i];
- // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings.
+ // BREAK, BREAKPOINT, CONSTANT, CONTINUE, and PASS are skipped because they can't contain translatable strings.
switch (statement->type) {
- case GDScriptParser::Node::VARIABLE:
- _assess_expression(static_cast<const GDScriptParser::VariableNode *>(statement)->initializer);
- break;
+ case GDScriptParser::Node::ASSERT: {
+ const GDScriptParser::AssertNode *assert_node = static_cast<const GDScriptParser::AssertNode *>(statement);
+ _assess_expression(assert_node->condition);
+ _assess_expression(assert_node->message);
+ } break;
+ case GDScriptParser::Node::ASSIGNMENT: {
+ _assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(statement));
+ } break;
+ case GDScriptParser::Node::FOR: {
+ const GDScriptParser::ForNode *for_node = static_cast<const GDScriptParser::ForNode *>(statement);
+ _assess_expression(for_node->list);
+ _traverse_block(for_node->loop);
+ } break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_node = static_cast<const GDScriptParser::IfNode *>(statement);
_assess_expression(if_node->condition);
- //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if().
_traverse_block(if_node->true_block);
_traverse_block(if_node->false_block);
- break;
- }
- case GDScriptParser::Node::FOR: {
- const GDScriptParser::ForNode *for_node = static_cast<const GDScriptParser::ForNode *>(statement);
- _assess_expression(for_node->list);
- _traverse_block(for_node->loop);
- break;
- }
- case GDScriptParser::Node::WHILE: {
- const GDScriptParser::WhileNode *while_node = static_cast<const GDScriptParser::WhileNode *>(statement);
- _assess_expression(while_node->condition);
- _traverse_block(while_node->loop);
- break;
- }
+ } break;
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match_node = static_cast<const GDScriptParser::MatchNode *>(statement);
_assess_expression(match_node->test);
for (int j = 0; j < match_node->branches.size(); j++) {
+ _traverse_block(match_node->branches[j]->guard_body);
_traverse_block(match_node->branches[j]->block);
}
- break;
- }
- case GDScriptParser::Node::RETURN:
+ } break;
+ case GDScriptParser::Node::RETURN: {
_assess_expression(static_cast<const GDScriptParser::ReturnNode *>(statement)->return_value);
- break;
- case GDScriptParser::Node::ASSERT:
- _assess_expression((static_cast<const GDScriptParser::AssertNode *>(statement))->condition);
- break;
- case GDScriptParser::Node::ASSIGNMENT:
- _assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(statement));
- break;
- default:
+ } break;
+ case GDScriptParser::Node::VARIABLE: {
+ _assess_expression(static_cast<const GDScriptParser::VariableNode *>(statement)->initializer);
+ } break;
+ case GDScriptParser::Node::WHILE: {
+ const GDScriptParser::WhileNode *while_node = static_cast<const GDScriptParser::WhileNode *>(statement);
+ _assess_expression(while_node->condition);
+ _traverse_block(while_node->loop);
+ } break;
+ default: {
if (statement->is_expression()) {
_assess_expression(static_cast<const GDScriptParser::ExpressionNode *>(statement));
}
- break;
+ } break;
}
}
}
@@ -172,25 +177,25 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar
return;
}
- // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode
- // containing translation strings.
+ // GET_NODE, IDENTIFIER, LITERAL, PRELOAD, SELF, and TYPE are skipped because they can't contain translatable strings.
switch (p_expression->type) {
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
for (int i = 0; i < array_node->elements.size(); i++) {
_assess_expression(array_node->elements[i]);
}
- break;
- }
- case GDScriptParser::Node::ASSIGNMENT:
+ } break;
+ case GDScriptParser::Node::ASSIGNMENT: {
_assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(p_expression));
- break;
+ } break;
+ case GDScriptParser::Node::AWAIT: {
+ _assess_expression(static_cast<const GDScriptParser::AwaitNode *>(p_expression)->to_await);
+ } break;
case GDScriptParser::Node::BINARY_OPERATOR: {
const GDScriptParser::BinaryOpNode *binary_op_node = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
_assess_expression(binary_op_node->left_operand);
_assess_expression(binary_op_node->right_operand);
- break;
- }
+ } break;
case GDScriptParser::Node::CALL: {
const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression);
_extract_from_call(call_node);
@@ -198,23 +203,40 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar
_assess_expression(call_node->arguments[i]);
}
} break;
+ case GDScriptParser::Node::CAST: {
+ _assess_expression(static_cast<const GDScriptParser::CastNode *>(p_expression)->operand);
+ } break;
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dict_node = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
for (int i = 0; i < dict_node->elements.size(); i++) {
_assess_expression(dict_node->elements[i].key);
_assess_expression(dict_node->elements[i].value);
}
- break;
- }
+ } break;
+ case GDScriptParser::Node::LAMBDA: {
+ _traverse_function(static_cast<const GDScriptParser::LambdaNode *>(p_expression)->function);
+ } break;
+ case GDScriptParser::Node::SUBSCRIPT: {
+ const GDScriptParser::SubscriptNode *subscript_node = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
+ _assess_expression(subscript_node->base);
+ if (!subscript_node->is_attribute) {
+ _assess_expression(subscript_node->index);
+ }
+ } break;
case GDScriptParser::Node::TERNARY_OPERATOR: {
const GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression);
_assess_expression(ternary_op_node->condition);
_assess_expression(ternary_op_node->true_expr);
_assess_expression(ternary_op_node->false_expr);
- break;
- }
- default:
- break;
+ } break;
+ case GDScriptParser::Node::TYPE_TEST: {
+ _assess_expression(static_cast<const GDScriptParser::TypeTestNode *>(p_expression)->operand);
+ } break;
+ case GDScriptParser::Node::UNARY_OPERATOR: {
+ _assess_expression(static_cast<const GDScriptParser::UnaryOpNode *>(p_expression)->operand);
+ } break;
+ default: {
+ } break;
}
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 580c2a80cd..fab79a925f 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -59,7 +59,6 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
- void _read_variable(const GDScriptParser::VariableNode *p_var);
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);
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 6245cc85a0..0da7752940 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -992,6 +992,7 @@ void GDScript::set_path(const String &p_path, bool p_take_over) {
String old_path = path;
path = p_path;
+ path_valid = true;
GDScriptCache::move_script(old_path, p_path);
for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
@@ -1000,6 +1001,9 @@ void GDScript::set_path(const String &p_path, bool p_take_over) {
}
String GDScript::get_script_path() const {
+ if (!path_valid && !get_path().is_empty()) {
+ return get_path();
+ }
return path;
}
@@ -1035,6 +1039,7 @@ Error GDScript::load_source_code(const String &p_path) {
source = s;
path = p_path;
+ path_valid = true;
#ifdef TOOLS_ENABLED
source_changed_cache = true;
set_edited(false);
@@ -1112,8 +1117,7 @@ GDScript *GDScript::find_class(const String &p_qualified_name) {
// 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];
- if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) {
+ if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(class_names[i])) {
result = E->value.ptr();
} else {
// Couldn't find inner class.
@@ -1151,8 +1155,8 @@ RBSet<GDScript *> GDScript::get_dependencies() {
return dependencies;
}
-RBSet<GDScript *> GDScript::get_inverted_dependencies() {
- RBSet<GDScript *> inverted_dependencies;
+HashMap<GDScript *, RBSet<GDScript *>> GDScript::get_all_dependencies() {
+ HashMap<GDScript *, RBSet<GDScript *>> all_dependencies;
List<GDScript *> scripts;
{
@@ -1166,51 +1170,42 @@ RBSet<GDScript *> GDScript::get_inverted_dependencies() {
}
for (GDScript *scr : scripts) {
- if (scr == nullptr || scr == this || scr->destructing) {
+ if (scr == nullptr || scr->destructing) {
continue;
}
-
- RBSet<GDScript *> scr_dependencies = scr->get_dependencies();
- if (scr_dependencies.has(this)) {
- inverted_dependencies.insert(scr);
- }
+ all_dependencies.insert(scr, scr->get_dependencies());
}
- return inverted_dependencies;
+ return all_dependencies;
}
RBSet<GDScript *> GDScript::get_must_clear_dependencies() {
RBSet<GDScript *> dependencies = get_dependencies();
RBSet<GDScript *> must_clear_dependencies;
- HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies;
-
- for (GDScript *E : dependencies) {
- inverted_dependencies.insert(E, E->get_inverted_dependencies());
- }
+ HashMap<GDScript *, RBSet<GDScript *>> all_dependencies = get_all_dependencies();
RBSet<GDScript *> cant_clear;
- for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : all_dependencies) {
+ if (dependencies.has(E.key)) {
+ continue;
+ }
for (GDScript *F : E.value) {
- if (!dependencies.has(F)) {
- cant_clear.insert(E.key);
- for (GDScript *G : E.key->get_dependencies()) {
- cant_clear.insert(G);
- }
- break;
+ if (dependencies.has(F)) {
+ cant_clear.insert(F);
}
}
}
- for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
- if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) {
+ for (GDScript *E : dependencies) {
+ if (cant_clear.has(E) || ScriptServer::is_global_class(E->get_fully_qualified_name())) {
continue;
}
- must_clear_dependencies.insert(E.key);
+ must_clear_dependencies.insert(E);
}
cant_clear.clear();
dependencies.clear();
- inverted_dependencies.clear();
+ all_dependencies.clear();
return must_clear_dependencies;
}
@@ -1386,34 +1381,44 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) {
}
#endif
-thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local;
+GDScript::UpdatableFuncPtr::UpdatableFuncPtr(GDScriptFunction *p_function) {
+ if (p_function == nullptr) {
+ return;
+ }
-GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
- UpdatableFuncPtrElement result = {};
+ ptr = p_function;
+ script = ptr->get_script();
+ ERR_FAIL_NULL(script);
- {
- 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.mutex = &func_ptrs_to_update_thread_local.mutex;
+ MutexLock script_lock(script->func_ptrs_to_update_mutex);
+ list_element = script->func_ptrs_to_update.push_back(this);
+}
- if (likely(func_ptrs_to_update_thread_local.initialized)) {
- return result;
- }
+GDScript::UpdatableFuncPtr::~UpdatableFuncPtr() {
+ ERR_FAIL_NULL(script);
- func_ptrs_to_update_thread_local.initialized = true;
+ if (list_element) {
+ MutexLock script_lock(script->func_ptrs_to_update_mutex);
+ list_element->erase();
+ list_element = nullptr;
}
+}
+void GDScript::_recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const {
MutexLock lock(func_ptrs_to_update_mutex);
- func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local);
-
- return result;
-}
+ 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;
+ }
+ }
-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.mutex);
- MutexLock lock(*p_func_ptr_element.mutex);
- p_func_ptr_element.element->erase();
+ for (HashMap<StringName, Ref<GDScript>>::ConstIterator subscript = subclasses.begin(); subscript; ++subscript) {
+ subscript->value->_recurse_replace_function_ptrs(p_replacements);
+ }
}
void GDScript::clear(ClearData *p_clear_data) {
@@ -1434,12 +1439,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) {
- MutexLock inner_lock(updatable->mutex);
- for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) {
- *func_ptr_ptr = nullptr;
- }
+ updatable->ptr = nullptr;
}
}
@@ -1512,6 +1514,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();
{
@@ -1656,7 +1665,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;
}
}
@@ -1665,9 +1674,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;
}
@@ -2145,7 +2154,7 @@ void GDScriptLanguage::finish() {
void GDScriptLanguage::profiling_start() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -2158,6 +2167,8 @@ void GDScriptLanguage::profiling_start() {
elem->self()->profile.last_frame_call_count = 0;
elem->self()->profile.last_frame_self_time = 0;
elem->self()->profile.last_frame_total_time = 0;
+ elem->self()->profile.native_calls.clear();
+ elem->self()->profile.last_native_calls.clear();
elem = elem->next();
}
@@ -2165,9 +2176,16 @@ void GDScriptLanguage::profiling_start() {
#endif
}
+void GDScriptLanguage::profiling_set_save_native_calls(bool p_enable) {
+#ifdef DEBUG_ENABLED
+ MutexLock lock(mutex);
+ profile_native_calls = p_enable;
+#endif
+}
+
void GDScriptLanguage::profiling_stop() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->mutex);
+ MutexLock lock(mutex);
profiling = false;
#endif
@@ -2177,19 +2195,34 @@ 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();
while (elem) {
if (current >= p_info_max) {
break;
}
+ int last_non_internal = current;
p_info_arr[current].call_count = elem->self()->profile.call_count.get();
p_info_arr[current].self_time = elem->self()->profile.self_time.get();
p_info_arr[current].total_time = elem->self()->profile.total_time.get();
p_info_arr[current].signature = elem->self()->profile.signature;
- elem = elem->next();
current++;
+
+ int nat_time = 0;
+ HashMap<String, GDScriptFunction::Profile::NativeProfile>::ConstIterator nat_calls = elem->self()->profile.native_calls.begin();
+ while (nat_calls) {
+ p_info_arr[current].call_count = nat_calls->value.call_count;
+ p_info_arr[current].total_time = nat_calls->value.total_time;
+ p_info_arr[current].self_time = nat_calls->value.total_time;
+ p_info_arr[current].signature = nat_calls->value.signature;
+ nat_time += nat_calls->value.total_time;
+ current++;
+ ++nat_calls;
+ }
+ p_info_arr[last_non_internal].internal_time = nat_time;
+ elem = elem->next();
}
#endif
@@ -2200,19 +2233,35 @@ 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();
while (elem) {
if (current >= p_info_max) {
break;
}
if (elem->self()->profile.last_frame_call_count > 0) {
+ int last_non_internal = current;
p_info_arr[current].call_count = elem->self()->profile.last_frame_call_count;
p_info_arr[current].self_time = elem->self()->profile.last_frame_self_time;
p_info_arr[current].total_time = elem->self()->profile.last_frame_total_time;
p_info_arr[current].signature = elem->self()->profile.signature;
current++;
+
+ int nat_time = 0;
+ HashMap<String, GDScriptFunction::Profile::NativeProfile>::ConstIterator nat_calls = elem->self()->profile.last_native_calls.begin();
+ while (nat_calls) {
+ p_info_arr[current].call_count = nat_calls->value.call_count;
+ p_info_arr[current].total_time = nat_calls->value.total_time;
+ p_info_arr[current].self_time = nat_calls->value.total_time;
+ p_info_arr[current].internal_time = nat_calls->value.total_time;
+ p_info_arr[current].signature = nat_calls->value.signature;
+ nat_time += nat_calls->value.total_time;
+ current++;
+ ++nat_calls;
+ }
+ p_info_arr[last_non_internal].internal_time = nat_time;
}
elem = elem->next();
}
@@ -2221,6 +2270,33 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_
return current;
}
+void GDScriptLanguage::profiling_collate_native_call_data(bool p_accumulated) {
+#ifdef DEBUG_ENABLED
+ // The same native call can be called from multiple functions, so join them together here.
+ // Only use the name of the function (ie signature.split[2]).
+ HashMap<String, GDScriptFunction::Profile::NativeProfile *> seen_nat_calls;
+ SelfList<GDScriptFunction> *elem = function_list.first();
+ while (elem) {
+ HashMap<String, GDScriptFunction::Profile::NativeProfile> *nat_calls = p_accumulated ? &elem->self()->profile.native_calls : &elem->self()->profile.last_native_calls;
+ HashMap<String, GDScriptFunction::Profile::NativeProfile>::Iterator it = nat_calls->begin();
+
+ while (it != nat_calls->end()) {
+ Vector<String> sig = it->value.signature.split("::");
+ HashMap<String, GDScriptFunction::Profile::NativeProfile *>::ConstIterator already_found = seen_nat_calls.find(sig[2]);
+ if (already_found) {
+ already_found->value->total_time += it->value.total_time;
+ already_found->value->call_count += it->value.call_count;
+ elem->self()->profile.last_native_calls.remove(it);
+ } else {
+ seen_nat_calls.insert(sig[2], &it->value);
+ }
+ ++it;
+ }
+ elem = elem->next();
+ }
+#endif
+}
+
struct GDScriptDepSort {
//must support sorting so inheritance works properly (parent must be reloaded first)
bool operator()(const Ref<GDScript> &A, const Ref<GDScript> &B) const {
@@ -2244,39 +2320,43 @@ 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
}
elem = elem->next();
}
- }
-
- //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);
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // Reload all pointers to existing singletons so that tool scripts can work with the reloaded extensions.
+ List<Engine::Singleton> singletons;
+ Engine::get_singleton()->get_singletons(&singletons);
+ for (const Engine::Singleton &E : singletons) {
+ if (globals.has(E.name)) {
+ _add_global(E.name, E.ptr);
+ }
+ }
+ }
+#endif
}
+
+ 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) {
@@ -2297,7 +2377,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;
@@ -2320,7 +2400,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()) {
@@ -2348,6 +2428,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
@@ -2395,21 +2477,29 @@ 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) {
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get();
elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get();
elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
+ elem->self()->profile.last_native_calls = elem->self()->profile.native_calls;
elem->self()->profile.frame_call_count.set(0);
elem->self()->profile.frame_self_time.set(0);
elem->self()->profile.frame_total_time.set(0);
+ elem->self()->profile.native_calls.clear();
elem = elem->next();
}
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 04b0a1d786..2da9b89eb9 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -117,22 +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 = false;
- };
- struct UpdatableFuncPtrElement {
- List<GDScriptFunction **>::Element *element = nullptr;
- Mutex *mutex = 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 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);
+ void _recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const;
#ifdef TOOLS_ENABLED
// For static data storage during hot-reloading.
@@ -171,6 +177,7 @@ class GDScript : public Script {
//exported members
String source;
String path;
+ bool path_valid = false; // False if using default path.
StringName local_name; // Inner class identifier or `class_name`.
StringName global_name; // `class_name`.
String fully_qualified_name;
@@ -246,7 +253,7 @@ public:
const Ref<GDScriptNativeClass> &get_native() const { return native; }
RBSet<GDScript *> get_dependencies();
- RBSet<GDScript *> get_inverted_dependencies();
+ HashMap<GDScript *, RBSet<GDScript *>> get_all_dependencies();
RBSet<GDScript *> get_must_clear_dependencies();
virtual bool has_script_signal(const StringName &p_signal) const override;
@@ -431,6 +438,7 @@ class GDScriptLanguage : public ScriptLanguage {
SelfList<GDScriptFunction>::List function_list;
bool profiling;
+ bool profile_native_calls;
uint64_t script_frame_time;
HashMap<String, ObjectID> orphan_subclasses;
@@ -532,7 +540,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
@@ -567,6 +575,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;
@@ -577,6 +586,8 @@ public:
virtual void profiling_start() override;
virtual void profiling_stop() override;
+ virtual void profiling_set_save_native_calls(bool p_enable) override;
+ void profiling_collate_native_call_data(bool p_accumulated);
virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override;
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 983a19470a..dd9e49fb8d 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -685,10 +685,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.builtin_type = GDScriptParser::get_builtin_type(first);
if (result.builtin_type == Variant::ARRAY) {
- GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
+ GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
container_type.is_constant = false;
- result.set_container_element_type(container_type);
+ result.set_container_element_type(0, container_type);
}
}
} else if (class_exists(first)) {
@@ -829,15 +829,23 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
}
}
- if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) {
- push_error("Only arrays can specify the collection element type.", p_type);
+ if (!p_type->container_types.is_empty()) {
+ if (result.builtin_type == Variant::ARRAY) {
+ if (p_type->container_types.size() != 1) {
+ push_error("Arrays require exactly one collection element type.", p_type);
+ return bad_type;
+ }
+ } else {
+ push_error("Only arrays can specify collection element types.", p_type);
+ return bad_type;
+ }
}
p_type->set_datatype(result);
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);
}
@@ -1891,8 +1899,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) {
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer);
- if (has_specified_type && specified_type.has_container_element_type()) {
- update_array_literal_element_type(array, specified_type.get_container_element_type());
+ if (has_specified_type && specified_type.has_container_element_type(0)) {
+ update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
}
@@ -1955,7 +1963,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
- } else if (specified_type.has_container_element_type() && !initializer_type.has_container_element_type()) {
+ } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@@ -2127,8 +2135,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
if (list_type.is_variant()) {
variable_type.kind = GDScriptParser::DataType::VARIANT;
mark_node_unsafe(p_for->list);
- } else if (list_type.has_container_element_type()) {
- variable_type = list_type.get_container_element_type();
+ } else if (list_type.has_container_element_type(0)) {
+ variable_type = list_type.get_container_element_type(0);
variable_type.type_source = list_type.type_source;
} else if (list_type.is_typed_container_type()) {
variable_type = list_type.get_typed_container_type();
@@ -2377,8 +2385,8 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
result.builtin_type = Variant::NIL;
result.is_constant = true;
} else {
- if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
+ if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@@ -2598,7 +2606,7 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr
// This function determines which type is that (if any).
void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) {
GDScriptParser::DataType expected_type = p_element_type;
- expected_type.unset_container_element_type(); // Nested types (like `Array[Array[int]]`) are not currently supported.
+ expected_type.container_element_types.clear(); // Nested types (like `Array[Array[int]]`) are not currently supported.
for (int i = 0; i < p_array->elements.size(); i++) {
GDScriptParser::ExpressionNode *element_node = p_array->elements[i];
@@ -2621,7 +2629,7 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
}
GDScriptParser::DataType array_type = p_array->get_datatype();
- array_type.set_container_element_type(expected_type);
+ array_type.set_container_element_type(0, expected_type);
p_array->set_datatype(array_type);
}
@@ -2668,8 +2676,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
- if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type());
+ if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@@ -2747,7 +2755,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
- } else if (assignee_type.has_container_element_type() && !op_type.has_container_element_type()) {
+ } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
// typed array assignee and untyped array result
mark_node_unsafe(p_assignment);
}
@@ -3311,8 +3319,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
// If the function requires typed arrays we must make literals be typed.
for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) {
int index = E.key;
- if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) {
- update_array_literal_element_type(E.value, par_types[index].get_container_element_type());
+ if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type(0)) {
+ update_array_literal_element_type(E.value, par_types[index].get_container_element_type(0));
}
}
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
@@ -3444,8 +3452,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
}
}
- if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type()) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type());
+ if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type(0)) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
if (!cast_type.is_variant()) {
@@ -3628,7 +3636,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;
}
@@ -3650,6 +3658,10 @@ 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();
@@ -3769,6 +3781,60 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
+ // Check non-GDScript scripts.
+ Ref<Script> script_type = base.script_type;
+
+ if (base_class == nullptr && script_type.is_valid()) {
+ List<PropertyInfo> property_list;
+ script_type->get_script_property_list(&property_list);
+
+ for (const PropertyInfo &property_info : property_list) {
+ if (property_info.name != p_identifier->name) {
+ continue;
+ }
+
+ const GDScriptParser::DataType property_type = GDScriptAnalyzer::type_from_property(property_info, false, false);
+
+ p_identifier->set_datatype(property_type);
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+ return;
+ }
+
+ MethodInfo method_info = script_type->get_method_info(p_identifier->name);
+
+ if (method_info.name == p_identifier->name) {
+ p_identifier->set_datatype(make_callable_type(method_info));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION;
+ return;
+ }
+
+ List<MethodInfo> signal_list;
+ script_type->get_script_signal_list(&signal_list);
+
+ for (const MethodInfo &signal_info : signal_list) {
+ if (signal_info.name != p_identifier->name) {
+ continue;
+ }
+
+ const GDScriptParser::DataType signal_type = make_signal_type(signal_info);
+
+ p_identifier->set_datatype(signal_type);
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
+ return;
+ }
+
+ HashMap<StringName, Variant> constant_map;
+ script_type->get_constants(&constant_map);
+
+ if (constant_map.has(p_identifier->name)) {
+ Variant constant = constant_map.get(p_identifier->name);
+
+ p_identifier->set_datatype(make_builtin_meta_type(constant.get_type()));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
+ return;
+ }
+ }
+
// Check native members. No need for native class recursion because Node exposes all Object's properties.
const StringName &native = base.native_type;
@@ -4432,8 +4498,8 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
break;
// Can have an element type.
case Variant::ARRAY:
- if (base_type.has_container_element_type()) {
- result_type = base_type.get_container_element_type();
+ if (base_type.has_container_element_type(0)) {
+ result_type = base_type.get_container_element_type(0);
result_type.type_source = base_type.type_source;
} else {
result_type.kind = GDScriptParser::DataType::VARIANT;
@@ -4597,7 +4663,7 @@ Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::Expressi
}
Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) {
- Array array = p_array->get_datatype().has_container_element_type() ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type()) : Array();
+ Array array = p_array->get_datatype().has_container_element_type(0) ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type(0)) : Array();
array.resize(p_array->elements.size());
for (int i = 0; i < p_array->elements.size(); i++) {
@@ -4719,8 +4785,8 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
GDScriptParser::DataType datatype = p_variable->get_datatype();
if (datatype.is_hard_type()) {
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
- if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) {
- result = make_array_from_element_datatype(datatype.get_container_element_type());
+ if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
+ result = make_array_from_element_datatype(datatype.get_container_element_type(0));
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@@ -4747,11 +4813,11 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
if (p_value.get_type() == Variant::ARRAY) {
const Array &array = p_value;
if (array.get_typed_script()) {
- result.set_container_element_type(type_from_metatype(make_script_meta_type(array.get_typed_script())));
+ result.set_container_element_type(0, type_from_metatype(make_script_meta_type(array.get_typed_script())));
} else if (array.get_typed_class_name()) {
- result.set_container_element_type(type_from_metatype(make_native_meta_type(array.get_typed_class_name())));
+ result.set_container_element_type(0, type_from_metatype(make_native_meta_type(array.get_typed_class_name())));
} else if (array.get_typed_builtin() != Variant::NIL) {
- result.set_container_element_type(type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
+ result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
@@ -4842,8 +4908,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;
@@ -4873,7 +4950,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array.");
}
elem_type.is_constant = false;
- result.set_container_element_type(elem_type);
+ result.set_container_element_type(0, elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@@ -5225,7 +5302,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
if (p_operation == Variant::OP_ADD && a_type == Variant::ARRAY && b_type == Variant::ARRAY) {
- if (p_a.has_container_element_type() && p_b.has_container_element_type() && p_a.get_container_element_type() == p_b.get_container_element_type()) {
+ if (p_a.has_container_element_type(0) && p_b.has_container_element_type(0) && p_a.get_container_element_type(0) == p_b.get_container_element_type(0)) {
r_valid = true;
result = p_a;
result.type_source = hard_operation ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
@@ -5249,8 +5326,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");
@@ -5276,8 +5366,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
}
if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) {
// Check the element type.
- if (p_target.has_container_element_type() && p_source.has_container_element_type()) {
- valid = p_target.get_container_element_type() == p_source.get_container_element_type();
+ if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
+ valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
return valid;
@@ -5285,11 +5375,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.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 25e20c0e76..27766115d5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -623,8 +623,8 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
switch (p_type.kind) {
case GDScriptDataType::BUILTIN: {
- if (p_type.builtin_type == Variant::ARRAY && p_type.has_container_element_type()) {
- const GDScriptDataType &element_type = p_type.get_container_element_type();
+ if (p_type.builtin_type == Variant::ARRAY && p_type.has_container_element_type(0)) {
+ const GDScriptDataType &element_type = p_type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_ARRAY);
append(p_target);
append(p_source);
@@ -878,8 +878,8 @@ void GDScriptByteCodeGenerator::write_get_static_variable(const Address &p_targe
void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
switch (p_target.type.kind) {
case GDScriptDataType::BUILTIN: {
- if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
- const GDScriptDataType &element_type = p_target.type.get_container_element_type();
+ if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type(0)) {
+ const GDScriptDataType &element_type = p_target.type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY);
append(p_target);
append(p_source);
@@ -924,8 +924,8 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
}
void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) {
- if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
- const GDScriptDataType &element_type = p_target.type.get_container_element_type();
+ if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type(0)) {
+ const GDScriptDataType &element_type = p_target.type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY);
append(p_target);
append(p_source);
@@ -1666,9 +1666,9 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
// If this is a typed function, then we need to check for potential conversions.
if (function->return_type.has_type) {
- if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) {
+ if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type(0)) {
// Typed array.
- const GDScriptDataType &element_type = function->return_type.get_container_element_type();
+ const GDScriptDataType &element_type = function->return_type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY);
append(p_return_value);
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
@@ -1691,8 +1691,8 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
} else {
switch (function->return_type.kind) {
case GDScriptDataType::BUILTIN: {
- if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) {
- const GDScriptDataType &element_type = function->return_type.get_container_element_type();
+ if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type(0)) {
+ const GDScriptDataType &element_type = function->return_type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY);
append(p_return_value);
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
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 26f01ec218..76f4e69ab9 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -364,28 +364,33 @@ void GDScriptCache::remove_static_script(const String &p_fqcn) {
Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
- if (singleton->packed_scene_cache.has(p_path)) {
- singleton->packed_scene_dependencies[p_path].insert(p_owner);
- return singleton->packed_scene_cache[p_path];
+ String path = p_path;
+ if (path.begins_with("uid://")) {
+ path = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(path));
}
- Ref<PackedScene> scene = ResourceCache::get_ref(p_path);
+ if (singleton->packed_scene_cache.has(path)) {
+ singleton->packed_scene_dependencies[path].insert(p_owner);
+ return singleton->packed_scene_cache[path];
+ }
+
+ Ref<PackedScene> scene = ResourceCache::get_ref(path);
if (scene.is_valid()) {
- singleton->packed_scene_cache[p_path] = scene;
- singleton->packed_scene_dependencies[p_path].insert(p_owner);
+ singleton->packed_scene_cache[path] = scene;
+ singleton->packed_scene_dependencies[path].insert(p_owner);
return scene;
}
scene.instantiate();
r_error = OK;
- if (p_path.is_empty()) {
+ if (path.is_empty()) {
r_error = ERR_FILE_BAD_PATH;
return scene;
}
- scene->set_path(p_path);
- singleton->packed_scene_cache[p_path] = scene;
- singleton->packed_scene_dependencies[p_path].insert(p_owner);
+ scene->set_path(path);
+ singleton->packed_scene_cache[path] = scene;
+ singleton->packed_scene_dependencies[path].insert(p_owner);
scene->reload_from_file();
return scene;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 7980f020b8..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) {
@@ -196,8 +197,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
}
- if (p_datatype.has_container_element_type()) {
- result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner, false));
+ for (int i = 0; i < p_datatype.container_element_types.size(); i++) {
+ result.set_container_element_type(i, _gdtype_from_datatype(p_datatype.get_container_element_type_or_variant(i), p_owner, false));
}
return result;
@@ -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);
@@ -507,8 +512,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
values.push_back(val);
}
- if (array_type.has_container_element_type()) {
- gen->write_construct_typed_array(result, array_type.get_container_element_type(), values);
+ if (array_type.has_container_element_type(0)) {
+ gen->write_construct_typed_array(result, array_type.get_container_element_type(0), values);
} else {
gen->write_construct_array(result, values);
}
@@ -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++) {
@@ -2133,8 +2138,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
initialized = true;
} else if (local_type.has_type) {
// Initialize with default for type.
- if (local_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ if (local_type.has_container_element_type(0)) {
+ codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
initialized = true;
} else if (local_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
@@ -2276,8 +2281,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
- if (field_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ if (field_type.has_container_element_type(0)) {
+ codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@@ -2466,9 +2471,9 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
- if (field_type.has_container_element_type()) {
+ if (field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
- codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
@@ -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 724715d9e5..44e104da05 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++) {
@@ -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,20 @@ 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_method_location(Ref<Script> p_script, const StringName &p_method) {
+ int depth = 0;
+ Ref<Script> scr = p_script;
+ while (scr.is_valid()) {
+ if (scr->get_member_line(p_method) != -1) {
+ return depth | ScriptLanguage::LOCATION_PARENT_MASK;
+ }
+ depth++;
+ scr = scr->get_base_script();
+ }
+ return depth + _get_method_location(p_script->get_instance_base_type(), p_method);
+}
+
+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 +672,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;
}
}
@@ -1083,7 +1135,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
List<PropertyInfo> members;
scr->get_script_property_list(&members);
for (const PropertyInfo &E : members) {
- int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name);
+ 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, E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
r_result.insert(option.display, option);
}
@@ -1091,7 +1149,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);
}
@@ -1099,7 +1157,7 @@ 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);
}
@@ -1111,7 +1169,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
if (E.name.begins_with("@")) {
continue;
}
- int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name);
+ int location = p_recursion_depth + _get_method_location(scr, E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
if (E.arguments.size()) {
option.insert_text += "(";
@@ -1152,7 +1210,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_GROUP | PROPERTY_USAGE_CATEGORY)) {
+ if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) {
continue;
}
if (E.name.contains("/")) {
@@ -1204,6 +1262,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) {
@@ -1213,8 +1273,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 | 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];
}
@@ -1230,7 +1297,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 {
@@ -1355,7 +1422,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;
@@ -1377,8 +1444,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;
@@ -1403,8 +1484,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;
}
@@ -1466,7 +1558,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:
@@ -1480,16 +1572,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;
@@ -1664,7 +1753,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()) {
@@ -1715,7 +1804,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;
}
}
@@ -1743,7 +1832,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;
}
@@ -1795,7 +1884,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;
}
@@ -1850,7 +1939,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;
@@ -1908,7 +1997,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;
@@ -1997,6 +2086,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) {
@@ -2050,8 +2154,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;
}
}
@@ -2109,20 +2220,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);
@@ -2143,7 +2240,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;
}
@@ -2153,7 +2250,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;
}
@@ -2206,7 +2303,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;
@@ -2233,7 +2330,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;
@@ -2269,7 +2366,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;
}
@@ -2333,7 +2430,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;
@@ -2601,6 +2698,64 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
r_arghint = _make_arguments_hint(info, p_argidx);
}
+ if (p_argidx == 1 && p_context.node && p_context.node->type == GDScriptParser::Node::CALL && ClassDB::is_parent_class(class_name, SNAME("Tween")) && p_method == SNAME("tween_property")) {
+ // Get tweened objects properties.
+ GDScriptParser::ExpressionNode *tweened_object = static_cast<GDScriptParser::CallNode *>(p_context.node)->arguments[0];
+ StringName native_type = tweened_object->datatype.native_type;
+ switch (tweened_object->datatype.kind) {
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> script = tweened_object->datatype.script_type;
+ native_type = script->get_instance_base_type();
+ int n = 0;
+ while (script.is_valid()) {
+ List<PropertyInfo> properties;
+ script->get_script_property_list(&properties);
+ for (const PropertyInfo &E : properties) {
+ if (E.usage & (PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_INTERNAL)) {
+ continue;
+ }
+ ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
+ r_result.insert(option.display, option);
+ }
+ script = script->get_base_script();
+ n++;
+ }
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+ GDScriptParser::ClassNode *clss = tweened_object->datatype.class_type;
+ native_type = clss->base_type.native_type;
+ int n = 0;
+ while (clss) {
+ for (GDScriptParser::ClassNode::Member member : clss->members) {
+ if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
+ ScriptLanguage::CodeCompletionOption option(member.get_name().quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
+ r_result.insert(option.display, option);
+ }
+ }
+ if (clss->base_type.kind == GDScriptParser::DataType::Kind::CLASS) {
+ clss = clss->base_type.class_type;
+ n++;
+ } else {
+ native_type = clss->base_type.native_type;
+ clss = nullptr;
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(native_type, &properties);
+ for (const PropertyInfo &E : properties) {
+ if (E.usage & (PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_INTERNAL)) {
+ continue;
+ }
+ ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER);
+ r_result.insert(option.display, option);
+ }
+ }
+
if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (p_method == SNAME("get_node") || p_method == SNAME("has_node"))) {
// Get autoloads
List<PropertyInfo> props;
@@ -2667,6 +2822,7 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
if (p_context.base == nullptr) {
return false;
}
+
const GDScriptParser::GetNodeNode *get_node = nullptr;
switch (p_subscript->base->type) {
@@ -2675,6 +2831,11 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
} break;
case GDScriptParser::Node::IDENTIFIER: {
+ if (p_subscript->base->datatype.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT) {
+ // Annotated type takes precedence.
+ return false;
+ }
+
const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base);
switch (identifier_node->source) {
@@ -2715,10 +2876,19 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
if (r_base != nullptr) {
*r_base = node;
}
- r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- r_base_type.kind = GDScriptParser::DataType::NATIVE;
+
+ r_base_type.type_source = GDScriptParser::DataType::INFERRED;
r_base_type.builtin_type = Variant::OBJECT;
r_base_type.native_type = node->get_class_name();
+
+ Ref<Script> scr = node->get_script();
+ if (scr.is_null()) {
+ r_base_type.kind = GDScriptParser::DataType::NATIVE;
+ } else {
+ r_base_type.kind = GDScriptParser::DataType::SCRIPT;
+ r_base_type.script_type = scr;
+ }
+
return true;
}
}
@@ -2745,8 +2915,6 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call);
GDScriptParser::Node::Type callee_type = call->get_callee_type();
- GDScriptCompletionIdentifier connect_base;
-
if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
@@ -3114,6 +3282,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();
@@ -3122,6 +3295,14 @@ 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();
}
+
+ if (for_unique_name) {
+ if (!opt.begins_with("%")) {
+ continue;
+ }
+ opt = opt.substr(1);
+ }
+
// 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()) {
@@ -3133,11 +3314,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;
@@ -3331,6 +3514,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;
@@ -3481,7 +3670,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_function.h b/modules/gdscript/gdscript_function.h
index c9b543fbb9..177c68533e 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -45,10 +45,9 @@ class GDScriptInstance;
class GDScript;
class GDScriptDataType {
-private:
- GDScriptDataType *container_element_type = nullptr;
-
public:
+ Vector<GDScriptDataType> container_element_types;
+
enum Kind {
UNINITIALIZED,
BUILTIN,
@@ -76,19 +75,20 @@ public:
case BUILTIN: {
Variant::Type var_type = p_variant.get_type();
bool valid = builtin_type == var_type;
- if (valid && builtin_type == Variant::ARRAY && has_container_element_type()) {
+ if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
Array array = p_variant;
if (array.is_typed()) {
+ GDScriptDataType array_container_type = get_container_element_type(0);
Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
StringName array_native_type = array.get_typed_class_name();
Ref<Script> array_script_type_ref = array.get_typed_script();
if (array_script_type_ref.is_valid()) {
- valid = (container_element_type->kind == SCRIPT || container_element_type->kind == GDSCRIPT) && container_element_type->script_type == array_script_type_ref.ptr();
+ valid = (array_container_type.kind == SCRIPT || array_container_type.kind == GDSCRIPT) && array_container_type.script_type == array_script_type_ref.ptr();
} else if (array_native_type != StringName()) {
- valid = container_element_type->kind == NATIVE && container_element_type->native_type == array_native_type;
+ valid = array_container_type.kind == NATIVE && array_container_type.native_type == array_native_type;
} else {
- valid = container_element_type->kind == BUILTIN && container_element_type->builtin_type == array_builtin_type;
+ valid = array_container_type.kind == BUILTIN && array_container_type.builtin_type == array_builtin_type;
}
} else {
valid = false;
@@ -147,24 +147,32 @@ public:
return false;
}
- void set_container_element_type(const GDScriptDataType &p_element_type) {
- container_element_type = memnew(GDScriptDataType(p_element_type));
+ void set_container_element_type(int p_index, const GDScriptDataType &p_element_type) {
+ ERR_FAIL_COND(p_index < 0);
+ while (p_index >= container_element_types.size()) {
+ container_element_types.push_back(GDScriptDataType());
+ }
+ container_element_types.write[p_index] = GDScriptDataType(p_element_type);
+ }
+
+ GDScriptDataType get_container_element_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, container_element_types.size(), GDScriptDataType());
+ return container_element_types[p_index];
}
- GDScriptDataType get_container_element_type() const {
- ERR_FAIL_NULL_V(container_element_type, GDScriptDataType());
- return *container_element_type;
+ GDScriptDataType get_container_element_type_or_variant(int p_index) const {
+ if (p_index < 0 || p_index >= container_element_types.size()) {
+ return GDScriptDataType();
+ }
+ return container_element_types[p_index];
}
- bool has_container_element_type() const {
- return container_element_type != nullptr;
+ bool has_container_element_type(int p_index) const {
+ return p_index >= 0 && p_index < container_element_types.size();
}
- void unset_container_element_type() {
- if (container_element_type) {
- memdelete(container_element_type);
- }
- container_element_type = nullptr;
+ bool has_container_element_types() const {
+ return !container_element_types.is_empty();
}
GDScriptDataType() = default;
@@ -176,19 +184,14 @@ public:
native_type = p_other.native_type;
script_type = p_other.script_type;
script_type_ref = p_other.script_type_ref;
- unset_container_element_type();
- if (p_other.has_container_element_type()) {
- set_container_element_type(p_other.get_container_element_type());
- }
+ container_element_types = p_other.container_element_types;
}
GDScriptDataType(const GDScriptDataType &p_other) {
*this = p_other;
}
- ~GDScriptDataType() {
- unset_container_element_type();
- }
+ ~GDScriptDataType() {}
};
class GDScriptFunction {
@@ -471,6 +474,13 @@ private:
uint64_t last_frame_call_count = 0;
uint64_t last_frame_self_time = 0;
uint64_t last_frame_total_time = 0;
+ typedef struct NativeProfile {
+ uint64_t call_count;
+ uint64_t total_time;
+ String signature;
+ } NativeProfile;
+ HashMap<String, NativeProfile> native_calls;
+ HashMap<String, NativeProfile> last_native_calls;
} profile;
#endif
@@ -511,6 +521,7 @@ public:
void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const;
#ifdef DEBUG_ENABLED
+ void _profile_native_call(uint64_t p_t_taken, const String &p_function_name, const String &p_instance_class_name = String());
void disassemble(const Vector<String> &p_code_lines) const;
#endif
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 db7b3e7ace..2839d7b123 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -73,8 +73,11 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
HashMap<String, String> GDScriptParser::theme_color_names;
#endif
+HashMap<StringName, GDScriptParser::AnnotationInfo> GDScriptParser::valid_annotations;
+
void GDScriptParser::cleanup() {
builtin_types.clear();
+ valid_annotations.clear();
}
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
@@ -89,41 +92,42 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {
GDScriptParser::GDScriptParser() {
// Register valid annotations.
- // TODO: Should this be static?
- register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
- register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
- register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
-
- register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
- // Export annotations.
- register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
- register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
- register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
- register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
- register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);
- register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
- register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
- register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
- register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true);
- register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true);
- register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
- register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true);
- register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true);
- register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
- // Export grouping annotations.
- register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
- register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
- register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
- // Warning annotations.
- register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
- // Networking.
- register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
+ if (unlikely(valid_annotations.is_empty())) {
+ register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
+ register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+ register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
+
+ register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
+ // Export annotations.
+ register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
+ register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
+ register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
+ register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);
+ register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true);
+ register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true);
+ register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
+ register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true);
+ register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true);
+ register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
+ // Export grouping annotations.
+ register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
+ register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
+ register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
+ // Warning annotations.
+ register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
+ // Networking.
+ register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
+ }
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
@@ -625,7 +629,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);
@@ -1116,7 +1120,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);
@@ -1264,8 +1273,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;
}
@@ -3337,14 +3345,21 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
// Typed collection (like Array[int]).
- type->container_type = parse_type(false); // Don't allow void for array element type.
- if (type->container_type == nullptr) {
- push_error(R"(Expected type for collection after "[".)");
- complete_extents(type);
- type = nullptr;
- } else if (type->container_type->container_type != nullptr) {
- push_error("Nested typed collections are not supported.");
- }
+ bool first_pass = true;
+ do {
+ TypeNode *container_type = parse_type(false); // Don't allow void for element type.
+ if (container_type == nullptr) {
+ push_error(vformat(R"(Expected type for collection after "%s".)", first_pass ? "[" : ","));
+ complete_extents(type);
+ type = nullptr;
+ break;
+ } else if (container_type->container_types.size() > 0) {
+ push_error("Nested typed collections are not supported.");
+ } else {
+ type->container_types.append(container_type);
+ }
+ first_pass = false;
+ } while (match(GDScriptTokenizer::Token::COMMA));
consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)");
if (type != nullptr) {
complete_extents(type);
@@ -3844,12 +3859,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;
}
@@ -3996,8 +4011,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.type = Variant::INT;
}
} else if (p_annotation->name == SNAME("@export_multiline")) {
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
- DataType inner_type = export_type.get_container_element_type();
+ if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
+ DataType inner_type = export_type.get_container_element_type(0);
if (inner_type.builtin_type != Variant::STRING) {
push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)", p_annotation->name.operator String(), inner_type.to_string()), variable);
return false;
@@ -4033,8 +4048,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
bool is_array = false;
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
- export_type = export_type.get_container_element_type(); // Use inner type for.
+ if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
+ export_type = export_type.get_container_element_type(0); // Use inner type for.
is_array = true;
}
@@ -4344,8 +4359,8 @@ String GDScriptParser::DataType::to_string() const {
if (builtin_type == Variant::NIL) {
return "null";
}
- if (builtin_type == Variant::ARRAY && has_container_element_type()) {
- return vformat("Array[%s]", container_element_type->to_string());
+ if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
+ return vformat("Array[%s]", container_element_types[0].to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@@ -4398,36 +4413,36 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
switch (kind) {
case BUILTIN:
result.type = builtin_type;
- if (builtin_type == Variant::ARRAY && has_container_element_type()) {
- const DataType *elem_type = container_element_type;
- switch (elem_type->kind) {
+ if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
+ const DataType elem_type = get_container_element_type(0);
+ switch (elem_type.kind) {
case BUILTIN:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
- result.hint_string = Variant::get_type_name(elem_type->builtin_type);
+ result.hint_string = Variant::get_type_name(elem_type.builtin_type);
break;
case NATIVE:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
- result.hint_string = elem_type->native_type;
+ result.hint_string = elem_type.native_type;
break;
case SCRIPT:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
- if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) {
- result.hint_string = elem_type->script_type->get_global_name();
+ if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) {
+ result.hint_string = elem_type.script_type->get_global_name();
} else {
- result.hint_string = elem_type->native_type;
+ result.hint_string = elem_type.native_type;
}
break;
case CLASS:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
- if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) {
- result.hint_string = elem_type->class_type->get_global_name();
+ if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) {
+ result.hint_string = elem_type.class_type->get_global_name();
} else {
- result.hint_string = elem_type->native_type;
+ result.hint_string = elem_type.native_type;
}
break;
case ENUM:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
- result.hint_string = String(elem_type->native_type).replace("::", ".");
+ result.hint_string = String(elem_type.native_type).replace("::", ".");
break;
case VARIANT:
case RESOLVING:
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 4b46b98baa..11c5e51b9a 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -101,11 +101,9 @@ public:
struct WhileNode;
class DataType {
- private:
- // Private access so we can control memory management.
- DataType *container_element_type = nullptr;
-
public:
+ Vector<DataType> container_element_types;
+
enum Kind {
BUILTIN,
NATIVE,
@@ -152,24 +150,39 @@ public:
_FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; }
PropertyInfo to_property_info(const String &p_name) const;
- _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) {
- container_element_type = memnew(DataType(p_type));
+ _FORCE_INLINE_ static DataType get_variant_type() { // Default DataType for container elements.
+ DataType datatype;
+ datatype.kind = VARIANT;
+ datatype.type_source = INFERRED;
+ return datatype;
}
- _FORCE_INLINE_ DataType get_container_element_type() const {
- ERR_FAIL_NULL_V(container_element_type, DataType());
- return *container_element_type;
+ _FORCE_INLINE_ void set_container_element_type(int p_index, const DataType &p_type) {
+ ERR_FAIL_COND(p_index < 0);
+ while (p_index >= container_element_types.size()) {
+ container_element_types.push_back(get_variant_type());
+ }
+ container_element_types.write[p_index] = DataType(p_type);
}
- _FORCE_INLINE_ bool has_container_element_type() const {
- return container_element_type != nullptr;
+ _FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
+ return container_element_types[p_index];
}
- _FORCE_INLINE_ void unset_container_element_type() {
- if (container_element_type) {
- memdelete(container_element_type);
- };
- container_element_type = nullptr;
+ _FORCE_INLINE_ DataType get_container_element_type_or_variant(int p_index) const {
+ if (p_index < 0 || p_index >= container_element_types.size()) {
+ return get_variant_type();
+ }
+ return container_element_types[p_index];
+ }
+
+ _FORCE_INLINE_ bool has_container_element_type(int p_index) const {
+ return p_index >= 0 && p_index < container_element_types.size();
+ }
+
+ _FORCE_INLINE_ bool has_container_element_types() const {
+ return !container_element_types.is_empty();
}
bool is_typed_container_type() const;
@@ -210,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) {
@@ -229,10 +242,7 @@ public:
class_type = p_other.class_type;
method_info = p_other.method_info;
enum_values = p_other.enum_values;
- unset_container_element_type();
- if (p_other.has_container_element_type()) {
- set_container_element_type(p_other.get_container_element_type());
- }
+ container_element_types = p_other.container_element_types;
}
DataType() = default;
@@ -241,9 +251,7 @@ public:
*this = p_other;
}
- ~DataType() {
- unset_container_element_type();
- }
+ ~DataType() {}
};
struct ParserError {
@@ -1183,7 +1191,11 @@ public:
struct TypeNode : public Node {
Vector<IdentifierNode *> type_chain;
- TypeNode *container_type = nullptr;
+ Vector<TypeNode *> container_types;
+
+ TypeNode *get_container_type_or_null(int p_index) const {
+ return p_index >= 0 && p_index < container_types.size() ? container_types[p_index] : nullptr;
+ }
TypeNode() {
type = TYPE;
@@ -1358,7 +1370,7 @@ private:
AnnotationAction apply = nullptr;
MethodInfo info;
};
- HashMap<StringName, AnnotationInfo> valid_annotations;
+ static HashMap<StringName, AnnotationInfo> valid_annotations;
List<AnnotationNode *> annotation_stack;
typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign);
@@ -1458,7 +1470,7 @@ private:
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
- bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
+ static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 6dd8a98652..a64aaf6820 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -37,6 +37,12 @@
#include "core/templates/vector.h"
#include "core/variant/variant.h"
+#ifdef MINGW_ENABLED
+#undef CONST
+#undef IN
+#undef VOID
+#endif
+
class GDScriptTokenizer {
public:
enum CursorPlace {
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 d31411b26b..1a8c22cc11 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -36,6 +36,18 @@
#include "core/os/os.h"
#ifdef DEBUG_ENABLED
+
+static bool _profile_count_as_native(const Object *p_base_obj, const StringName &p_methodname) {
+ if (!p_base_obj) {
+ return false;
+ }
+ StringName cname = p_base_obj->get_class_name();
+ if ((p_methodname == "new" && cname == "GDScript") || p_methodname == "call") {
+ return false;
+ }
+ return ClassDB::class_exists(cname) && ClassDB::has_method(cname, p_methodname, false);
+}
+
static String _get_element_type(Variant::Type builtin_type, const StringName &native_type, const Ref<Script> &script_type) {
if (script_type.is_valid() && script_type->is_valid()) {
return GDScript::debug_get_script_name(script_type);
@@ -84,6 +96,18 @@ static String _get_var_type(const Variant *p_var) {
return basestr;
}
+
+void GDScriptFunction::_profile_native_call(uint64_t p_t_taken, const String &p_func_name, const String &p_instance_class_name) {
+ HashMap<String, Profile::NativeProfile>::Iterator inner_prof = profile.native_calls.find(p_func_name);
+ if (inner_prof) {
+ inner_prof->value.call_count += 1;
+ } else {
+ String sig = vformat("%s::0::%s%s%s", get_script()->get_script_path(), p_instance_class_name, p_instance_class_name.is_empty() ? "" : ".", p_func_name);
+ inner_prof = profile.native_calls.insert(p_func_name, Profile::NativeProfile{ 1, 0, sig });
+ }
+ inner_prof->value.total_time += p_t_taken;
+}
+
#endif // DEBUG_ENABLED
Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataType &p_data_type) {
@@ -91,8 +115,8 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
if (p_data_type.builtin_type == Variant::ARRAY) {
Array array;
// Typed array.
- if (p_data_type.has_container_element_type()) {
- const GDScriptDataType &element_type = p_data_type.get_container_element_type();
+ if (p_data_type.has_container_element_type(0)) {
+ const GDScriptDataType &element_type = p_data_type.get_container_element_type(0);
array.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
}
@@ -631,10 +655,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
bool exit_ok = false;
bool awaited = false;
-#endif
-
-#ifdef DEBUG_ENABLED
- int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
+ int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? (int)p_instance->members.size() : 0 };
#endif
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
@@ -852,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();
@@ -870,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;
}
@@ -901,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
@@ -951,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);
@@ -964,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;
@@ -1000,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;
@@ -1065,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;
}
@@ -1110,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;
@@ -1661,16 +1693,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (GDScriptLanguage::get_singleton()->profiling) {
call_time = OS::get_singleton()->get_ticks_usec();
}
-
+ Variant::Type base_type = base->get_type();
+ Object *base_obj = base->get_validated_object();
+ StringName base_class = base_obj ? base_obj->get_class_name() : StringName();
#endif
+
Callable::CallError err;
if (call_ret) {
GET_INSTRUCTION_ARG(ret, argc + 1);
-#ifdef DEBUG_ENABLED
- Variant::Type base_type = base->get_type();
- Object *base_obj = base->get_validated_object();
- StringName base_class = base_obj ? base_obj->get_class_name() : StringName();
-#endif
base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err);
#ifdef DEBUG_ENABLED
if (ret->get_type() == Variant::NIL) {
@@ -1704,8 +1734,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
base->callp(*methodname, (const Variant **)argptrs, argc, ret, err);
}
#ifdef DEBUG_ENABLED
+
if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time;
+ if (GDScriptLanguage::get_singleton()->profile_native_calls && _profile_count_as_native(base_obj, *methodname)) {
+ _profile_native_call(t_taken, *methodname, base_class);
+ }
+ function_call_time += t_taken;
}
if (err.error != Callable::CallError::CALL_OK) {
@@ -1782,8 +1817,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
call_time = OS::get_singleton()->get_ticks_usec();
}
#endif
@@ -1797,8 +1831,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
+ uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time;
+ _profile_native_call(t_taken, method->get_name(), method->get_instance_class());
+ function_call_time += t_taken;
}
if (err.error != Callable::CallError::CALL_OK) {
@@ -1851,22 +1888,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
const Variant **argptrs = const_cast<const Variant **>(instruction_args);
-#ifdef DEBUG_ENABLED
- uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
- call_time = OS::get_singleton()->get_ticks_usec();
- }
-#endif
-
Callable::CallError err;
Variant::call_static(builtin_type, *methodname, argptrs, argc, *ret, err);
#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
- }
-
if (err.error != Callable::CallError::CALL_OK) {
err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs);
OPCODE_BREAK;
@@ -1895,8 +1920,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
call_time = OS::get_singleton()->get_ticks_usec();
}
#endif
@@ -1905,15 +1929,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
*ret = method->call(nullptr, argptrs, argc, err);
#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
+ uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time;
+ _profile_native_call(t_taken, method->get_name(), method->get_instance_class());
+ function_call_time += t_taken;
}
+#endif
if (err.error != Callable::CallError::CALL_OK) {
err_text = _get_call_error(err, "static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs);
OPCODE_BREAK;
}
-#endif
ip += 3;
}
@@ -1951,8 +1977,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
call_time = OS::get_singleton()->get_ticks_usec();
}
#endif
@@ -1961,10 +1986,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
method->validated_call(base_obj, (const Variant **)argptrs, ret);
#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
+ uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time;
+ _profile_native_call(t_taken, method->get_name(), method->get_instance_class());
+ function_call_time += t_taken;
}
#endif
+
ip += 3;
}
DISPATCH_OPCODE;
@@ -1998,8 +2026,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant **argptrs = instruction_args;
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
call_time = OS::get_singleton()->get_ticks_usec();
}
#endif
@@ -2009,10 +2036,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
method->validated_call(base_obj, (const Variant **)argptrs, nullptr);
#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) {
+ uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time;
+ _profile_native_call(t_taken, method->get_name(), method->get_instance_class());
+ function_call_time += t_taken;
}
#endif
+
ip += 3;
}
DISPATCH_OPCODE;
@@ -2033,22 +2063,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant::ValidatedBuiltInMethod method = _builtin_methods_ptr[_code_ptr[ip + 2]];
Variant **argptrs = instruction_args;
-#ifdef DEBUG_ENABLED
- uint64_t call_time = 0;
- if (GDScriptLanguage::get_singleton()->profiling) {
- call_time = OS::get_singleton()->get_ticks_usec();
- }
-#endif
-
GET_INSTRUCTION_ARG(ret, argc + 1);
method(base, (const Variant **)argptrs, argc, ret);
-#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
- }
-#endif
-
ip += 3;
}
DISPATCH_OPCODE;
@@ -2467,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
@@ -2497,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;
}
@@ -2530,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;
}
@@ -2548,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;
@@ -2571,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;
@@ -2593,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;
@@ -2612,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..0f8648e9a3 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++;
@@ -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 44f605232d..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());
}
}
@@ -315,9 +315,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
if (param_symbols.size() >= 2) {
- String class_ = param_symbols[0];
- StringName class_name = class_;
- String member_name = param_symbols[param_symbols.size() - 1];
+ StringName class_name = param_symbols[0];
+ const String &member_name = param_symbols[param_symbols.size() - 1];
String inner_class_name;
if (param_symbols.size() >= 3) {
inner_class_name = param_symbols[1];
@@ -422,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;
}
@@ -430,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()) {
@@ -457,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/tests/README.md b/modules/gdscript/tests/README.md
index 361d586d32..cea251bab5 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 behaviour 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 behaviour. (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 f91dc83f2c..4d93a6fc18 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -78,31 +78,30 @@ void init_autoloads() {
scn.instantiate();
scn->set_path(info.path);
scn->reload_from_file();
- ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path));
+ ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
if (scn.is_valid()) {
n = scn->instantiate();
}
} else {
Ref<Resource> res = ResourceLoader::load(info.path);
- ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path));
+ ERR_CONTINUE_MSG(res.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
Ref<Script> scr = res;
if (scr.is_valid()) {
StringName ibt = scr->get_instance_base_type();
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
- ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path));
+ ERR_CONTINUE_MSG(!valid_type, vformat("Failed to instantiate an autoload, script '%s' does not inherit from 'Node'.", info.path));
Object *obj = ClassDB::instantiate(ibt);
-
- ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt));
+ ERR_CONTINUE_MSG(!obj, vformat("Failed to instantiate an autoload, cannot instantiate '%s'.", ibt));
n = Object::cast_to<Node>(obj);
n->set_script(scr);
}
}
- ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path));
+ ERR_CONTINUE_MSG(!n, vformat("Failed to instantiate an autoload, path is not pointing to a scene or a script: %s.", info.path));
n->set_name(info.name);
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -267,7 +266,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;
}
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/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/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_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/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 7fe3a57880..d7e5ddd32c 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -67,6 +67,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
} else {
// use defaults
}
+ } else if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_DIRECTX) {
+ // NIR-DXIL is Vulkan 1.1-conformant.
+ ClientVersion = glslang::EShTargetVulkan_1_1;
+ // The SPIR-V part of Mesa supports 1.6, but:
+ // - SPIRV-Reflect won't be able to parse the compute workgroup size.
+ // - We want to play it safe with NIR-DXIL.
+ TargetVersion = glslang::EShTargetSpv_1_3;
} else {
// once we support other backends we'll need to do something here
if (r_error) {
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 8e4a72e6ae..ba7323b7cd 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -1,13 +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/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml
index c1fe85c1c0..65335250aa 100644
--- a/modules/gltf/doc_classes/GLTFAnimation.xml
+++ b/modules/gltf/doc_classes/GLTFAnimation.xml
@@ -5,6 +5,7 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
<member name="loop" type="bool" setter="set_loop" getter="get_loop" default="false">
diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml
index ce19650b5b..11d58bda72 100644
--- a/modules/gltf/doc_classes/GLTFBufferView.xml
+++ b/modules/gltf/doc_classes/GLTFBufferView.xml
@@ -1,21 +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/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml
index c2ed4d08e3..b334bf2867 100644
--- a/modules/gltf/doc_classes/GLTFCamera.xml
+++ b/modules/gltf/doc_classes/GLTFCamera.xml
@@ -7,6 +7,7 @@
Represents a camera as defined by the base GLTF spec.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link>
<link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link>
</tutorials>
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 5c10b76e0a..1b52a82298 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -9,6 +9,7 @@
GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="glTF 'What the duck?' guide">https://www.khronos.org/files/gltf20-reference-guide.pdf</link>
<link title="Khronos glTF specification">https://registry.khronos.org/glTF/</link>
</tutorials>
@@ -83,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 eee62845ca..0eabcb5022 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -9,6 +9,7 @@
[b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode].
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="_convert_scene_node" qualifiers="virtual">
@@ -29,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">
@@ -113,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/GLTFDocumentExtensionConvertImporterMesh.xml b/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml
index 3ca0359311..04075caba5 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml
@@ -5,5 +5,6 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
</class>
diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml
index c55962eeaa..653a394ebb 100644
--- a/modules/gltf/doc_classes/GLTFLight.xml
+++ b/modules/gltf/doc_classes/GLTFLight.xml
@@ -7,6 +7,7 @@
Represents a light as defined by the [code]KHR_lights_punctual[/code] GLTF extension.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link>
</tutorials>
<methods>
diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml
index df4d436f5c..da05474ab9 100644
--- a/modules/gltf/doc_classes/GLTFMesh.xml
+++ b/modules/gltf/doc_classes/GLTFMesh.xml
@@ -5,6 +5,7 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
<member name="blend_weights" type="PackedFloat32Array" setter="set_blend_weights" getter="get_blend_weights" default="PackedFloat32Array()">
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index 2ec39801f3..184ced4312 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -8,6 +8,7 @@
GLTF nodes generally exist inside of [GLTFState] which represents all data of a GLTF file. Most of GLTFNode's properties are indices of other data in the GLTF file. You can extend a GLTF node with additional properties by using [method get_additional_data] and [method set_additional_data].
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link>
</tutorials>
<methods>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index d364069193..ca66cd54b0 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -4,9 +4,10 @@
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>
<link title="OMI_physics_body GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link>
</tutorials>
<methods>
@@ -14,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">
@@ -27,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">
@@ -41,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 2891fab115..c397c660d9 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
@@ -4,10 +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="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider</link>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</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">
@@ -27,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/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml
index c7b8cb2ac3..ac03a6ee9e 100644
--- a/modules/gltf/doc_classes/GLTFSkeleton.xml
+++ b/modules/gltf/doc_classes/GLTFSkeleton.xml
@@ -5,6 +5,7 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="get_bone_attachment">
diff --git a/modules/gltf/doc_classes/GLTFSkin.xml b/modules/gltf/doc_classes/GLTFSkin.xml
index 98436ea2e7..0835a2d510 100644
--- a/modules/gltf/doc_classes/GLTFSkin.xml
+++ b/modules/gltf/doc_classes/GLTFSkin.xml
@@ -5,6 +5,7 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="get_inverse_binds">
diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml
index d64a918920..722fa5e9ae 100644
--- a/modules/gltf/doc_classes/GLTFSpecGloss.xml
+++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml
@@ -7,6 +7,7 @@
KHR_materials_pbrSpecularGlossiness is an archived GLTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="KHR_materials_pbrSpecularGlossiness GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link>
</tutorials>
<members>
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index ba1c531283..100750f400 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -8,6 +8,7 @@
GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a GLTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the GLTF file or for custom data to be converted to/from Godot nodes.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
<link title="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link>
</tutorials>
<methods>
@@ -19,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/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml
index 41c20439ea..dd323b753b 100644
--- a/modules/gltf/doc_classes/GLTFTexture.xml
+++ b/modules/gltf/doc_classes/GLTFTexture.xml
@@ -5,6 +5,7 @@
<description>
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
<member name="sampler" type="int" setter="set_sampler" getter="get_sampler" default="-1">
diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml
index 027d35e9d2..2b5bad6724 100644
--- a/modules/gltf/doc_classes/GLTFTextureSampler.xml
+++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml
@@ -7,6 +7,7 @@
Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
<member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" default="9729">
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 cb45a6589e..a91856c4a1 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -38,32 +38,24 @@
#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"
-#ifdef WINDOWS_ENABLED
-#include <shlwapi.h>
+#ifdef MINGW_ENABLED
+#define near
+#define far
#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");
- }
+#include <shlwapi.h>
#endif
- if (!FileAccess::exists(path)) {
+static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
+ if (!FileAccess::exists(p_path)) {
if (r_err) {
*r_err = TTR("Path does not contain a Blender installation.");
}
@@ -72,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.");
@@ -82,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;
}
@@ -121,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);
@@ -129,9 +121,22 @@ 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", p_path.get_file().get_basename(), p_path.md5_text()));
+ vformat("%s-%s.gltf", blend_basename, p_path.md5_text()));
const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape();
// Handle configuration options.
@@ -282,6 +287,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) {
base_dir = sink.get_base_dir();
}
+ state->set_scene_name(blend_basename);
err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
if (err != OK) {
if (r_err) {
@@ -350,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;
}
@@ -390,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;
}
@@ -408,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();
@@ -460,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 = "";
-
-#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");
- }
- }
+ String path = EDITOR_GET("filesystem/import/blender/blender_path");
-#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);
@@ -550,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..37b8ae0634 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))) {
+ 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 b5adea7da0..2acca5b856 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -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];
@@ -452,10 +453,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);
}
@@ -573,7 +577,7 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) {
// Determine what to use for the scene name.
if (scene_dict.has("name") && !String(scene_dict["name"]).is_empty() && !((String)scene_dict["name"]).begins_with("Scene")) {
p_state->scene_name = scene_dict["name"];
- } else {
+ } else if (p_state->scene_name.is_empty()) {
p_state->scene_name = p_state->filename;
}
if (_naming_version == 0) {
@@ -3218,7 +3222,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 +3234,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 +3664,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 (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;
+ if (metallness_image->is_compressed()) {
+ metallness_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;
+ }
+ }
+ 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();
@@ -3845,7 +3853,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;
@@ -5045,7 +5053,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);
}
}
@@ -6071,22 +6079,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();
}
@@ -6151,9 +6159,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);
@@ -6284,6 +6292,9 @@ 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++;
}
}
@@ -6306,6 +6317,9 @@ 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++;
}
}
@@ -6328,6 +6342,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++;
}
}
@@ -7052,9 +7069,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 +7353,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) {
@@ -7454,11 +7475,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..f321bb7111 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;
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 fecea45fc9..216309abf1 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -51,27 +51,43 @@
static void _editor_init() {
Ref<EditorSceneFormatImporterGLTF> import_gltf;
import_gltf.instantiate();
- ResourceImporterScene::add_importer(import_gltf);
+ ResourceImporterScene::add_scene_importer(import_gltf);
// 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_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());
@@ -82,7 +98,7 @@ static void _editor_init() {
if (fbx_enabled) {
Ref<EditorSceneFormatImporterFBX> importer;
importer.instantiate();
- ResourceImporterScene::get_scene_singleton()->add_importer(importer);
+ ResourceImporterScene::add_scene_importer(importer);
Ref<EditorFileSystemImportFormatSupportQueryFBX> fbx_import_query;
fbx_import_query.instantiate();
@@ -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.cpp b/modules/gltf/structures/gltf_buffer_view.cpp
index 7678f23f57..8588de0752 100644
--- a/modules/gltf/structures/gltf_buffer_view.cpp
+++ b/modules/gltf/structures/gltf_buffer_view.cpp
@@ -30,7 +30,11 @@
#include "gltf_buffer_view.h"
+#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);
@@ -88,3 +92,13 @@ bool GLTFBufferView::get_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..e4b7168130 100644
--- a/modules/gltf/structures/gltf_buffer_view.h
+++ b/modules/gltf/structures/gltf_buffer_view.h
@@ -64,7 +64,8 @@ public:
bool get_indices();
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_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/gridmap/grid_map.h b/modules/gridmap/grid_map.h
index e05979efbc..348ac5194c 100644
--- a/modules/gridmap/grid_map.h
+++ b/modules/gridmap/grid_map.h
@@ -155,7 +155,6 @@ class GridMap : public Node3D {
Ref<PhysicsMaterial> physics_material;
bool bake_navigation = false;
RID map_override;
- uint32_t navigation_layers = 1;
Transform3D last_transform;
diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp
index 0b9fcf4455..ada0cd01fa 100644
--- a/modules/jpg/image_loader_jpegd.cpp
+++ b/modules/jpg/image_loader_jpegd.cpp
@@ -156,8 +156,13 @@ public:
static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, const Ref<Image> &p_img, float p_quality) {
ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), ERR_INVALID_PARAMETER);
- Ref<Image> image = p_img;
+ Ref<Image> image = p_img->duplicate();
+ if (image->is_compressed()) {
+ Error error = image->decompress();
+ 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);
}
@@ -169,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 fe919953c1..02b5aefc9f 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -357,7 +357,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 +703,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 +756,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 +769,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");
@@ -877,7 +877,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
if (err != OK || exitcode != 0) {
da->remove(fname_out);
print_verbose(str);
- ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat(TTR("OIDN denoiser failed, return code: %d"), exitcode));
+ ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat("OIDN denoiser failed, return code: %d", exitcode));
}
Ref<Image> img = _read_pfm(fname_out);
@@ -939,8 +939,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 +958,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();
@@ -988,7 +988,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
oidn_path = oidn_path.path_join("oidnDenoise");
}
}
- ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings."));
+ ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, "OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings.");
}
if (p_step_function) {
@@ -1399,7 +1399,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 +1592,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 +1614,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 +1700,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 +1711,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();
@@ -1863,7 +1863,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);
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/register_types.cpp b/modules/minimp3/register_types.cpp
index 627d093bc1..c85f0b3389 100644
--- a/modules/minimp3/register_types.cpp
+++ b/modules/minimp3/register_types.cpp
@@ -52,8 +52,13 @@ void initialize_minimp3_module(ModuleInitializationLevel p_level) {
ResourceFormatImporter::get_singleton()->add_importer(mp3_import);
}
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
// Required to document import options in the class reference.
GDREGISTER_CLASS(ResourceImporterMP3);
+
+ ClassDB::set_current_api(prev_api);
#endif
GDREGISTER_CLASS(AudioStreamMP3);
diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp
index d60b979c3f..33c926689a 100644
--- a/modules/minimp3/resource_importer_mp3.cpp
+++ b/modules/minimp3/resource_importer_mp3.cpp
@@ -89,7 +89,7 @@ bool ResourceImporterMP3::has_advanced_options() const {
void ResourceImporterMP3::show_advanced_options(const String &p_path) {
Ref<AudioStreamMP3> mp3_stream = import_mp3(p_path);
if (mp3_stream.is_valid()) {
- AudioStreamImportSettings::get_singleton()->edit(p_path, "mp3", mp3_stream);
+ AudioStreamImportSettingsDialog::get_singleton()->edit(p_path, "mp3", mp3_stream);
}
}
#endif
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 56e4fa53d0..a84febb226 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++) {
@@ -697,19 +697,25 @@ struct CSharpScriptDepSort {
// Shouldn't happen but just in case...
return false;
}
- const Script *I = B->get_base_script().ptr();
+ const CSharpScript *I = get_base_script(B.ptr()).ptr();
while (I) {
if (I == A.ptr()) {
// A is a base of B
return true;
}
- I = I->get_base_script().ptr();
+ I = get_base_script(I).ptr();
}
// A isn't a base of B
return false;
}
+
+ // Special fix for constructed generic types.
+ Ref<CSharpScript> get_base_script(const CSharpScript *p_script) const {
+ Ref<CSharpScript> base_script = p_script->base_script;
+ return base_script.is_valid() && !base_script->class_name.is_empty() ? base_script : nullptr;
+ }
};
void CSharpLanguage::reload_all_scripts() {
@@ -720,11 +726,22 @@ 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) {
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
+ bool has_csharp_script = false;
+ for (int i = 0; i < p_scripts.size(); ++i) {
+ Ref<CSharpScript> cs_script = p_scripts[i];
+ if (cs_script.is_valid()) {
+ has_csharp_script = true;
+ break;
+ }
+ }
+
+ if (!has_csharp_script) {
+ return;
+ }
+
#ifdef TOOLS_ENABLED
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
@@ -736,6 +753,12 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
#endif
}
+void CSharpLanguage::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);
+}
+
#ifdef GD_MONO_HOT_RELOAD
bool CSharpLanguage::is_assembly_reloading_needed() {
ERR_FAIL_NULL_V(gdmono, false);
@@ -1074,7 +1097,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
@@ -2246,6 +2269,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 +2333,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();
@@ -2354,6 +2388,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++) {
@@ -2620,12 +2656,12 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
}
Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- ERR_FAIL_COND_V(!valid, Variant());
-
- Variant ret;
- bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret);
- if (ok) {
- return ret;
+ if (valid) {
+ Variant ret;
+ bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret);
+ if (ok) {
+ return ret;
+ }
}
return Script::callp(p_method, p_args, p_argcount, r_error);
@@ -2653,7 +2689,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();
@@ -2819,15 +2855,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) {
@@ -2867,8 +2905,11 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_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 e381f0e840..918776a6ba 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -60,6 +60,7 @@ class CSharpScript : public Script {
friend class CSharpInstance;
friend class CSharpLanguage;
+ friend struct CSharpScriptDepSort;
bool tool = false;
bool global_class = false;
@@ -423,7 +424,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;
@@ -463,6 +464,7 @@ public:
/* PROFILING FUNCTIONS */
/* TODO */ void profiling_start() override {}
/* TODO */ void profiling_stop() override {}
+ /* TODO */ void profiling_set_save_native_calls(bool p_enable) override {}
/* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override {
return 0;
}
@@ -477,6 +479,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.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
index ad3a10ba49..ccef90c911 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -7,7 +7,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.NET.Sdk</PackageId>
- <Version>4.2.0</Version>
+ <Version>4.3.0</Version>
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
index d8129a6652..b51ce5cb8c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
@@ -33,9 +33,9 @@
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
<ItemGroup>
- <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
+ <LinkerArg Include="-mios-simulator-version-min=12.0 -isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
- <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
+ <LinkerArg Include="-miphoneos-version-min=12.0 -isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
Condition=" !$(RuntimeIdentifier.Contains('simulator')) "/>
</ItemGroup>
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/CSharpSourceGeneratorVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs
new file mode 100644
index 0000000000..d75922481c
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators.Tests;
+
+public static class CSharpSourceGeneratorVerifier<TSourceGenerator>
+where TSourceGenerator : ISourceGenerator, new()
+{
+ public class Test : CSharpSourceGeneratorTest<TSourceGenerator, XUnitVerifier>
+ {
+ public Test()
+ {
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+
+ SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
+ {
+ Project project = solution.GetProject(projectId)!
+ .AddMetadataReference(Constants.GodotSharpAssembly.CreateMetadataReference());
+
+ return project.Solution;
+ });
+ }
+ }
+
+ public static Task Verify(string source, params string[] generatedSources)
+ {
+ return Verify(new string[] { source }, generatedSources);
+ }
+
+ public static Task VerifyNoCompilerDiagnostics(string source, params string[] generatedSources)
+ {
+ return VerifyNoCompilerDiagnostics(new string[] { source }, generatedSources);
+ }
+
+ public static Task Verify(ICollection<string> sources, params string[] generatedSources)
+ {
+ return MakeVerifier(sources, generatedSources).RunAsync();
+ }
+
+ public static Task VerifyNoCompilerDiagnostics(ICollection<string> sources, params string[] generatedSources)
+ {
+ var verifier = MakeVerifier(sources, generatedSources);
+ verifier.CompilerDiagnostics = CompilerDiagnostics.None;
+ return verifier.RunAsync();
+ }
+
+ public static Test MakeVerifier(ICollection<string> sources, ICollection<string> generatedSources)
+ {
+ 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.TestState.GeneratedSources.AddRange(generatedSources.Select(generatedSource =>
+ {
+ return (FullGeneratedSourceName(generatedSource), SourceText.From(File.ReadAllText(Path.Combine(Constants.GeneratedSourceFolderPath, generatedSource)), Encoding.UTF8));
+ }));
+
+ return verifier;
+ }
+
+ private static string FullGeneratedSourceName(string name)
+ {
+ var generatorType = typeof(TSourceGenerator);
+ return Path.Combine(generatorType.Namespace!, generatorType.FullName!, name);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Constants.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Constants.cs
new file mode 100644
index 0000000000..783b1e4298
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Constants.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using System.Reflection;
+
+namespace Godot.SourceGenerators.Tests;
+
+public static class Constants
+{
+ public static Assembly GodotSharpAssembly => typeof(GodotObject).Assembly;
+
+ public static string ExecutingAssemblyPath { get; }
+ public static string SourceFolderPath { get; }
+ public static string GeneratedSourceFolderPath { get; }
+
+ static Constants()
+ {
+ ExecutingAssemblyPath = Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!);
+
+ var testDataPath = Path.Combine(ExecutingAssemblyPath, "TestData");
+
+ SourceFolderPath = Path.Combine(testDataPath, "Sources");
+ GeneratedSourceFolderPath = Path.Combine(testDataPath, "GeneratedSources");
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Extensions.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Extensions.cs
new file mode 100644
index 0000000000..6ff890e5d8
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Extensions.cs
@@ -0,0 +1,12 @@
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace Godot.SourceGenerators.Tests;
+
+public static class Extensions
+{
+ public static MetadataReference CreateMetadataReference(this Assembly assembly)
+ {
+ return MetadataReference.CreateFromFile(assembly.Location);
+ }
+}
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
new file mode 100644
index 0000000000..32d7ca7486
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+
+ <LangVersion>11</LangVersion>
+
+ <Nullable>enable</Nullable>
+ <IsPackable>false</IsPackable>
+ <IsTestProject>true</IsTestProject>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <DefaultItemExcludesInProjectFolder>$(DefaultItemExcludesInProjectFolder);TestData\**</DefaultItemExcludesInProjectFolder>
+ </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>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="coverlet.collector" Version="3.2.0">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj" />
+ <ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Include="TestData\**\*.cs" CopyToOutputDirectory="PreserveNewest" />
+ </ItemGroup>
+
+</Project>
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/ScriptMethodsGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptMethodsGeneratorTests.cs
new file mode 100644
index 0000000000..294932eb9a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptMethodsGeneratorTests.cs
@@ -0,0 +1,24 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptMethodsGeneratorTests
+{
+ [Fact]
+ public async void Methods()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptMethodsGenerator>.Verify(
+ "Methods.cs",
+ "Methods_ScriptMethods.generated.cs"
+ );
+ }
+
+ [Fact]
+ public async void ScriptBoilerplate()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptMethodsGenerator>.Verify(
+ "ScriptBoilerplate.cs",
+ "ScriptBoilerplate_ScriptMethods.generated.cs", "OuterClass.NestedClass_ScriptMethods.generated.cs"
+ );
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs
new file mode 100644
index 0000000000..b7ad4217aa
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis.Text;
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptPathAttributeGeneratorTests
+{
+ private static (string, SourceText) MakeAssemblyScriptTypesGeneratedSource(ICollection<string> types)
+ {
+ return (
+ Path.Combine("Godot.SourceGenerators", "Godot.SourceGenerators.ScriptPathAttributeGenerator", "AssemblyScriptTypes.generated.cs"),
+ SourceText.From($$"""
+ [assembly:Godot.AssemblyHasScriptsAttribute(new System.Type[] {{{string.Join(", ", types.Select(type => $"typeof({type})"))}}})]
+
+ """, Encoding.UTF8)
+ );
+ }
+
+ [Fact]
+ public async void ScriptBoilerplate()
+ {
+ var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
+ new string[] { "ScriptBoilerplate.cs" },
+ new string[] { "ScriptBoilerplate_ScriptPath.generated.cs" }
+ );
+ verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::ScriptBoilerplate" }));
+ await verifier.RunAsync();
+ }
+
+ [Fact]
+ public async void FooBar()
+ {
+ var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
+ new string[] { "Foo.cs", "Bar.cs" },
+ new string[] { "Foo_ScriptPath.generated.cs", "Bar_ScriptPath.generated.cs" }
+ );
+ verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Foo", "global::Bar" }));
+ await verifier.RunAsync();
+ }
+
+ [Fact]
+ public async void Generic()
+ {
+ var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
+ new string[] { "Generic.cs" },
+ new string[] { "Generic_ScriptPath.generated.cs" }
+ );
+ verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic" }));
+ await verifier.RunAsync();
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs
new file mode 100644
index 0000000000..d20b354b9e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs
@@ -0,0 +1,60 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptPropertiesGeneratorTests
+{
+ [Fact]
+ public async void ExportedFields()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ new string[] { "ExportedFields.cs", "MoreExportedFields.cs" },
+ new string[] { "ExportedFields_ScriptProperties.generated.cs" }
+ );
+ }
+
+ [Fact]
+ public async void ExportedProperties()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "ExportedProperties.cs",
+ "ExportedProperties_ScriptProperties.generated.cs"
+ );
+ }
+
+ [Fact]
+ public async void OneWayPropertiesAllReadOnly()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "AllReadOnly.cs",
+ "AllReadOnly_ScriptProperties.generated.cs"
+ );
+ }
+
+ [Fact]
+ public async void OneWayPropertiesAllWriteOnly()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "AllWriteOnly.cs",
+ "AllWriteOnly_ScriptProperties.generated.cs"
+ );
+ }
+
+ [Fact]
+ public async void OneWayPropertiesMixedReadonlyWriteOnly()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "MixedReadOnlyWriteOnly.cs",
+ "MixedReadOnlyWriteOnly_ScriptProperties.generated.cs"
+ );
+ }
+
+ [Fact]
+ public async void ScriptBoilerplate()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "ScriptBoilerplate.cs",
+ "ScriptBoilerplate_ScriptProperties.generated.cs", "OuterClass.NestedClass_ScriptProperties.generated.cs"
+ );
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs
new file mode 100644
index 0000000000..ae5fb86d77
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs
@@ -0,0 +1,24 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptPropertyDefValGeneratorTests
+{
+ [Fact]
+ public async void ExportedFields()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertyDefValGenerator>.Verify(
+ new string[] { "ExportedFields.cs", "MoreExportedFields.cs" },
+ new string[] { "ExportedFields_ScriptPropertyDefVal.generated.cs" }
+ );
+ }
+
+ [Fact]
+ public async void ExportedProperties()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertyDefValGenerator>.Verify(
+ "ExportedProperties.cs",
+ "ExportedProperties_ScriptPropertyDefVal.generated.cs"
+ );
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs
new file mode 100644
index 0000000000..24cd446087
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs
@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptSerializationGeneratorTests
+{
+ [Fact]
+ public async void ScriptBoilerplate()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptSerializationGenerator>.VerifyNoCompilerDiagnostics(
+ "ScriptBoilerplate.cs",
+ "ScriptBoilerplate_ScriptSerialization.generated.cs", "OuterClass.NestedClass_ScriptSerialization.generated.cs"
+ );
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSignalsGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSignalsGeneratorTests.cs
new file mode 100644
index 0000000000..2a783cedce
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSignalsGeneratorTests.cs
@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class ScriptSignalsGeneratorTests
+{
+ [Fact]
+ public async void EventSignals()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptSignalsGenerator>.Verify(
+ "EventSignals.cs",
+ "EventSignals_ScriptSignals.generated.cs"
+ );
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/.editorconfig b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/.editorconfig
new file mode 100644
index 0000000000..9e2014e16c
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*.cs]
+exclude = true
+generated_code = true
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..db56ac30a3
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs
@@ -0,0 +1,66 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class AllReadOnly
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'readonly_auto_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_auto_property = "readonly_auto_property";
+ /// <summary>
+ /// Cached name for the 'readonly_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_property = "readonly_property";
+ /// <summary>
+ /// Cached name for the 'initonly_auto_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName initonly_auto_property = "initonly_auto_property";
+ /// <summary>
+ /// Cached name for the 'readonly_field' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_field = "readonly_field";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.readonly_auto_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_auto_property);
+ return true;
+ }
+ else if (name == PropertyName.readonly_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_property);
+ return true;
+ }
+ else if (name == PropertyName.initonly_auto_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.initonly_auto_property);
+ return true;
+ }
+ else if (name == PropertyName.readonly_field) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_field);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_field, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_auto_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.initonly_auto_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..256420fe87
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs
@@ -0,0 +1,58 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class AllWriteOnly
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'writeonly_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName writeonly_property = "writeonly_property";
+ /// <summary>
+ /// Cached name for the 'writeonly_backing_field' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName writeonly_backing_field = "writeonly_backing_field";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.writeonly_property) {
+ this.writeonly_property = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.writeonly_backing_field) {
+ this.writeonly_backing_field = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.writeonly_backing_field) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.writeonly_backing_field);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.writeonly_backing_field, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.writeonly_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Bar_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Bar_ScriptPath.generated.cs
new file mode 100644
index 0000000000..9096da1b42
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Bar_ScriptPath.generated.cs
@@ -0,0 +1,5 @@
+using Godot;
+[ScriptPathAttribute("res://Bar.cs")]
+partial class Bar
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
new file mode 100644
index 0000000000..b1c57e6b26
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
@@ -0,0 +1,54 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class EventSignals
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the signals contained in this class, for fast lookup.
+ /// </summary>
+ public new class SignalName : global::Godot.GodotObject.SignalName {
+ /// <summary>
+ /// Cached name for the 'MySignal' signal.
+ /// </summary>
+ public new static readonly global::Godot.StringName MySignal = "MySignal";
+ }
+ /// <summary>
+ /// Get the signal information for all the signals declared in this class.
+ /// This method is used by Godot to register the available signals in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotSignalList()
+ {
+ var signals = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(1);
+ signals.Add(new(name: SignalName.MySignal, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)4, name: "str", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "num", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ return signals;
+ }
+#pragma warning restore CS0109
+ private global::EventSignals.MySignalEventHandler backing_MySignal;
+ /// <inheritdoc cref="global::EventSignals.MySignalEventHandler"/>
+ public event global::EventSignals.MySignalEventHandler MySignal {
+ add => backing_MySignal += value;
+ remove => backing_MySignal -= value;
+}
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args)
+ {
+ if (signal == SignalName.MySignal && args.Count == 2) {
+ backing_MySignal?.Invoke(global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(args[0]), global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[1]));
+ return;
+ }
+ base.RaiseGodotClassSignalCallbacks(signal, args);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool HasGodotClassSignal(in godot_string_name signal)
+ {
+ if (signal == SignalName.MySignal) {
+ return true;
+ }
+ return base.HasGodotClassSignal(signal);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..915c36525b
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -0,0 +1,816 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportedFields
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'field_Boolean' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Boolean = "field_Boolean";
+ /// <summary>
+ /// Cached name for the 'field_Char' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Char = "field_Char";
+ /// <summary>
+ /// Cached name for the 'field_SByte' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_SByte = "field_SByte";
+ /// <summary>
+ /// Cached name for the 'field_Int16' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Int16 = "field_Int16";
+ /// <summary>
+ /// Cached name for the 'field_Int32' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Int32 = "field_Int32";
+ /// <summary>
+ /// Cached name for the 'field_Int64' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Int64 = "field_Int64";
+ /// <summary>
+ /// Cached name for the 'field_Byte' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Byte = "field_Byte";
+ /// <summary>
+ /// Cached name for the 'field_UInt16' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_UInt16 = "field_UInt16";
+ /// <summary>
+ /// Cached name for the 'field_UInt32' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_UInt32 = "field_UInt32";
+ /// <summary>
+ /// Cached name for the 'field_UInt64' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_UInt64 = "field_UInt64";
+ /// <summary>
+ /// Cached name for the 'field_Single' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Single = "field_Single";
+ /// <summary>
+ /// Cached name for the 'field_Double' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Double = "field_Double";
+ /// <summary>
+ /// Cached name for the 'field_String' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_String = "field_String";
+ /// <summary>
+ /// Cached name for the 'field_Vector2' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector2 = "field_Vector2";
+ /// <summary>
+ /// Cached name for the 'field_Vector2I' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector2I = "field_Vector2I";
+ /// <summary>
+ /// Cached name for the 'field_Rect2' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Rect2 = "field_Rect2";
+ /// <summary>
+ /// Cached name for the 'field_Rect2I' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Rect2I = "field_Rect2I";
+ /// <summary>
+ /// Cached name for the 'field_Transform2D' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Transform2D = "field_Transform2D";
+ /// <summary>
+ /// Cached name for the 'field_Vector3' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector3 = "field_Vector3";
+ /// <summary>
+ /// Cached name for the 'field_Vector3I' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector3I = "field_Vector3I";
+ /// <summary>
+ /// Cached name for the 'field_Basis' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Basis = "field_Basis";
+ /// <summary>
+ /// Cached name for the 'field_Quaternion' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Quaternion = "field_Quaternion";
+ /// <summary>
+ /// Cached name for the 'field_Transform3D' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Transform3D = "field_Transform3D";
+ /// <summary>
+ /// Cached name for the 'field_Vector4' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector4 = "field_Vector4";
+ /// <summary>
+ /// Cached name for the 'field_Vector4I' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector4I = "field_Vector4I";
+ /// <summary>
+ /// Cached name for the 'field_Projection' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Projection = "field_Projection";
+ /// <summary>
+ /// Cached name for the 'field_Aabb' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Aabb = "field_Aabb";
+ /// <summary>
+ /// Cached name for the 'field_Color' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Color = "field_Color";
+ /// <summary>
+ /// Cached name for the 'field_Plane' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Plane = "field_Plane";
+ /// <summary>
+ /// Cached name for the 'field_Callable' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Callable = "field_Callable";
+ /// <summary>
+ /// Cached name for the 'field_Signal' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Signal = "field_Signal";
+ /// <summary>
+ /// Cached name for the 'field_Enum' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Enum = "field_Enum";
+ /// <summary>
+ /// Cached name for the 'field_FlagsEnum' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_FlagsEnum = "field_FlagsEnum";
+ /// <summary>
+ /// Cached name for the 'field_ByteArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_ByteArray = "field_ByteArray";
+ /// <summary>
+ /// Cached name for the 'field_Int32Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Int32Array = "field_Int32Array";
+ /// <summary>
+ /// Cached name for the 'field_Int64Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Int64Array = "field_Int64Array";
+ /// <summary>
+ /// Cached name for the 'field_SingleArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_SingleArray = "field_SingleArray";
+ /// <summary>
+ /// Cached name for the 'field_DoubleArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_DoubleArray = "field_DoubleArray";
+ /// <summary>
+ /// Cached name for the 'field_StringArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_StringArray = "field_StringArray";
+ /// <summary>
+ /// Cached name for the 'field_StringArrayEnum' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_StringArrayEnum = "field_StringArrayEnum";
+ /// <summary>
+ /// Cached name for the 'field_Vector2Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector2Array = "field_Vector2Array";
+ /// <summary>
+ /// Cached name for the 'field_Vector3Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Vector3Array = "field_Vector3Array";
+ /// <summary>
+ /// Cached name for the 'field_ColorArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_ColorArray = "field_ColorArray";
+ /// <summary>
+ /// Cached name for the 'field_GodotObjectOrDerivedArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotObjectOrDerivedArray = "field_GodotObjectOrDerivedArray";
+ /// <summary>
+ /// Cached name for the 'field_StringNameArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_StringNameArray = "field_StringNameArray";
+ /// <summary>
+ /// Cached name for the 'field_NodePathArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_NodePathArray = "field_NodePathArray";
+ /// <summary>
+ /// Cached name for the 'field_RidArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_RidArray = "field_RidArray";
+ /// <summary>
+ /// Cached name for the 'field_empty_Int32Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_empty_Int32Array = "field_empty_Int32Array";
+ /// <summary>
+ /// Cached name for the 'field_array_from_list' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_array_from_list = "field_array_from_list";
+ /// <summary>
+ /// Cached name for the 'field_Variant' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Variant = "field_Variant";
+ /// <summary>
+ /// Cached name for the 'field_GodotObjectOrDerived' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotObjectOrDerived = "field_GodotObjectOrDerived";
+ /// <summary>
+ /// Cached name for the 'field_GodotResourceTexture' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotResourceTexture = "field_GodotResourceTexture";
+ /// <summary>
+ /// Cached name for the 'field_StringName' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_StringName = "field_StringName";
+ /// <summary>
+ /// Cached name for the 'field_NodePath' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_NodePath = "field_NodePath";
+ /// <summary>
+ /// Cached name for the 'field_Rid' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_Rid = "field_Rid";
+ /// <summary>
+ /// Cached name for the 'field_GodotDictionary' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotDictionary = "field_GodotDictionary";
+ /// <summary>
+ /// Cached name for the 'field_GodotArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotArray = "field_GodotArray";
+ /// <summary>
+ /// Cached name for the 'field_GodotGenericDictionary' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotGenericDictionary = "field_GodotGenericDictionary";
+ /// <summary>
+ /// Cached name for the 'field_GodotGenericArray' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_GodotGenericArray = "field_GodotGenericArray";
+ /// <summary>
+ /// Cached name for the 'field_empty_Int64Array' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_empty_Int64Array = "field_empty_Int64Array";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.field_Boolean) {
+ this.field_Boolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Char) {
+ this.field_Char = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_SByte) {
+ this.field_SByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Int16) {
+ this.field_Int16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Int32) {
+ this.field_Int32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Int64) {
+ this.field_Int64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Byte) {
+ this.field_Byte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt16) {
+ this.field_UInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt32) {
+ this.field_UInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt64) {
+ this.field_UInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Single) {
+ this.field_Single = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Double) {
+ this.field_Double = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_String) {
+ this.field_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2) {
+ this.field_Vector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2I) {
+ this.field_Vector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Rect2) {
+ this.field_Rect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Rect2I) {
+ this.field_Rect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Transform2D) {
+ this.field_Transform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3) {
+ this.field_Vector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3I) {
+ this.field_Vector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Basis) {
+ this.field_Basis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Quaternion) {
+ this.field_Quaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Transform3D) {
+ this.field_Transform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector4) {
+ this.field_Vector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector4I) {
+ this.field_Vector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Projection) {
+ this.field_Projection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Aabb) {
+ this.field_Aabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Color) {
+ this.field_Color = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Plane) {
+ this.field_Plane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Callable) {
+ this.field_Callable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Signal) {
+ this.field_Signal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Enum) {
+ this.field_Enum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyEnum>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_FlagsEnum) {
+ this.field_FlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyFlagsEnum>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_ByteArray) {
+ this.field_ByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Int32Array) {
+ this.field_Int32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Int64Array) {
+ this.field_Int64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_SingleArray) {
+ this.field_SingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_DoubleArray) {
+ this.field_DoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_StringArray) {
+ this.field_StringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_StringArrayEnum) {
+ this.field_StringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2Array) {
+ this.field_Vector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3Array) {
+ this.field_Vector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_ColorArray) {
+ this.field_ColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotObjectOrDerivedArray) {
+ this.field_GodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_StringNameArray) {
+ this.field_StringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePathArray) {
+ this.field_NodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_RidArray) {
+ this.field_RidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_empty_Int32Array) {
+ this.field_empty_Int32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_array_from_list) {
+ this.field_array_from_list = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Variant) {
+ this.field_Variant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotObjectOrDerived) {
+ this.field_GodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotResourceTexture) {
+ this.field_GodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_StringName) {
+ this.field_StringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePath) {
+ this.field_NodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_Rid) {
+ this.field_Rid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotDictionary) {
+ this.field_GodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotArray) {
+ this.field_GodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotGenericDictionary) {
+ this.field_GodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotGenericArray) {
+ this.field_GodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_empty_Int64Array) {
+ this.field_empty_Int64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.field_Boolean) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.field_Boolean);
+ return true;
+ }
+ else if (name == PropertyName.field_Char) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this.field_Char);
+ return true;
+ }
+ else if (name == PropertyName.field_SByte) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this.field_SByte);
+ return true;
+ }
+ else if (name == PropertyName.field_Int16) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this.field_Int16);
+ return true;
+ }
+ else if (name == PropertyName.field_Int32) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.field_Int32);
+ return true;
+ }
+ else if (name == PropertyName.field_Int64) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this.field_Int64);
+ return true;
+ }
+ else if (name == PropertyName.field_Byte) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this.field_Byte);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt16) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this.field_UInt16);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt32) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this.field_UInt32);
+ return true;
+ }
+ else if (name == PropertyName.field_UInt64) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this.field_UInt64);
+ return true;
+ }
+ else if (name == PropertyName.field_Single) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this.field_Single);
+ return true;
+ }
+ else if (name == PropertyName.field_Double) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this.field_Double);
+ return true;
+ }
+ else if (name == PropertyName.field_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.field_String);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this.field_Vector2);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this.field_Vector2I);
+ return true;
+ }
+ else if (name == PropertyName.field_Rect2) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this.field_Rect2);
+ return true;
+ }
+ else if (name == PropertyName.field_Rect2I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this.field_Rect2I);
+ return true;
+ }
+ else if (name == PropertyName.field_Transform2D) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this.field_Transform2D);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this.field_Vector3);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this.field_Vector3I);
+ return true;
+ }
+ else if (name == PropertyName.field_Basis) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this.field_Basis);
+ return true;
+ }
+ else if (name == PropertyName.field_Quaternion) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this.field_Quaternion);
+ return true;
+ }
+ else if (name == PropertyName.field_Transform3D) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this.field_Transform3D);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector4) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this.field_Vector4);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector4I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this.field_Vector4I);
+ return true;
+ }
+ else if (name == PropertyName.field_Projection) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this.field_Projection);
+ return true;
+ }
+ else if (name == PropertyName.field_Aabb) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this.field_Aabb);
+ return true;
+ }
+ else if (name == PropertyName.field_Color) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this.field_Color);
+ return true;
+ }
+ else if (name == PropertyName.field_Plane) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this.field_Plane);
+ return true;
+ }
+ else if (name == PropertyName.field_Callable) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.field_Callable);
+ return true;
+ }
+ else if (name == PropertyName.field_Signal) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this.field_Signal);
+ return true;
+ }
+ else if (name == PropertyName.field_Enum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyEnum>(this.field_Enum);
+ return true;
+ }
+ else if (name == PropertyName.field_FlagsEnum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyFlagsEnum>(this.field_FlagsEnum);
+ return true;
+ }
+ else if (name == PropertyName.field_ByteArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this.field_ByteArray);
+ return true;
+ }
+ else if (name == PropertyName.field_Int32Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.field_Int32Array);
+ return true;
+ }
+ else if (name == PropertyName.field_Int64Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.field_Int64Array);
+ return true;
+ }
+ else if (name == PropertyName.field_SingleArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this.field_SingleArray);
+ return true;
+ }
+ else if (name == PropertyName.field_DoubleArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this.field_DoubleArray);
+ return true;
+ }
+ else if (name == PropertyName.field_StringArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.field_StringArray);
+ return true;
+ }
+ else if (name == PropertyName.field_StringArrayEnum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.field_StringArrayEnum);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector2Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this.field_Vector2Array);
+ return true;
+ }
+ else if (name == PropertyName.field_Vector3Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this.field_Vector3Array);
+ return true;
+ }
+ else if (name == PropertyName.field_ColorArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this.field_ColorArray);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotObjectOrDerivedArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this.field_GodotObjectOrDerivedArray);
+ return true;
+ }
+ else if (name == PropertyName.field_StringNameArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this.field_StringNameArray);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePathArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this.field_NodePathArray);
+ return true;
+ }
+ else if (name == PropertyName.field_RidArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this.field_RidArray);
+ return true;
+ }
+ else if (name == PropertyName.field_empty_Int32Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.field_empty_Int32Array);
+ return true;
+ }
+ else if (name == PropertyName.field_array_from_list) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.field_array_from_list);
+ return true;
+ }
+ else if (name == PropertyName.field_Variant) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this.field_Variant);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotObjectOrDerived) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this.field_GodotObjectOrDerived);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotResourceTexture) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this.field_GodotResourceTexture);
+ return true;
+ }
+ else if (name == PropertyName.field_StringName) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this.field_StringName);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePath) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.field_NodePath);
+ return true;
+ }
+ else if (name == PropertyName.field_Rid) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this.field_Rid);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotDictionary) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this.field_GodotDictionary);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this.field_GodotArray);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotGenericDictionary) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this.field_GodotGenericDictionary);
+ return true;
+ }
+ else if (name == PropertyName.field_GodotGenericArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.field_GodotGenericArray);
+ return true;
+ }
+ else if (name == PropertyName.field_empty_Int64Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.field_empty_Int64Array);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.field_Boolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Char, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_SByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Int16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Int32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Int64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Byte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_UInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_UInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_UInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.field_Single, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.field_Double, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.field_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName.field_Vector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName.field_Vector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName.field_Rect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName.field_Rect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName.field_Transform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName.field_Vector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName.field_Vector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName.field_Basis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName.field_Quaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName.field_Transform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName.field_Vector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName.field_Vector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName.field_Projection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName.field_Aabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName.field_Color, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName.field_Plane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.field_Callable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName.field_Signal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_Enum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.field_FlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName.field_ByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.field_Int32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.field_Int64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName.field_SingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName.field_DoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.field_StringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.field_StringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName.field_Vector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName.field_Vector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName.field_ColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_GodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_StringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_NodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_RidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.field_empty_Int32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.field_array_from_list, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName.field_Variant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.field_GodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.field_GodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName.field_StringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.field_NodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.field_Rid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.field_GodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_GodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.field_GodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_GodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.field_empty_Int64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs
new file mode 100644
index 0000000000..185b8e4ac6
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs
@@ -0,0 +1,139 @@
+partial class ExportedFields
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+#if TOOLS
+ /// <summary>
+ /// Get the default values for all properties declared in this class.
+ /// This method is used by Godot to determine the value that will be
+ /// used by the inspector when resetting properties.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues()
+ {
+ var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(60);
+ bool __field_Boolean_default_value = true;
+ values.Add(PropertyName.field_Boolean, global::Godot.Variant.From<bool>(__field_Boolean_default_value));
+ char __field_Char_default_value = 'f';
+ values.Add(PropertyName.field_Char, global::Godot.Variant.From<char>(__field_Char_default_value));
+ sbyte __field_SByte_default_value = 10;
+ values.Add(PropertyName.field_SByte, global::Godot.Variant.From<sbyte>(__field_SByte_default_value));
+ short __field_Int16_default_value = 10;
+ values.Add(PropertyName.field_Int16, global::Godot.Variant.From<short>(__field_Int16_default_value));
+ int __field_Int32_default_value = 10;
+ values.Add(PropertyName.field_Int32, global::Godot.Variant.From<int>(__field_Int32_default_value));
+ long __field_Int64_default_value = 10;
+ values.Add(PropertyName.field_Int64, global::Godot.Variant.From<long>(__field_Int64_default_value));
+ byte __field_Byte_default_value = 10;
+ values.Add(PropertyName.field_Byte, global::Godot.Variant.From<byte>(__field_Byte_default_value));
+ ushort __field_UInt16_default_value = 10;
+ values.Add(PropertyName.field_UInt16, global::Godot.Variant.From<ushort>(__field_UInt16_default_value));
+ uint __field_UInt32_default_value = 10;
+ values.Add(PropertyName.field_UInt32, global::Godot.Variant.From<uint>(__field_UInt32_default_value));
+ ulong __field_UInt64_default_value = 10;
+ values.Add(PropertyName.field_UInt64, global::Godot.Variant.From<ulong>(__field_UInt64_default_value));
+ float __field_Single_default_value = 10;
+ values.Add(PropertyName.field_Single, global::Godot.Variant.From<float>(__field_Single_default_value));
+ double __field_Double_default_value = 10;
+ values.Add(PropertyName.field_Double, global::Godot.Variant.From<double>(__field_Double_default_value));
+ string __field_String_default_value = "foo";
+ values.Add(PropertyName.field_String, global::Godot.Variant.From<string>(__field_String_default_value));
+ global::Godot.Vector2 __field_Vector2_default_value = new(10f, 10f);
+ values.Add(PropertyName.field_Vector2, global::Godot.Variant.From<global::Godot.Vector2>(__field_Vector2_default_value));
+ global::Godot.Vector2I __field_Vector2I_default_value = global::Godot.Vector2I.Up;
+ values.Add(PropertyName.field_Vector2I, global::Godot.Variant.From<global::Godot.Vector2I>(__field_Vector2I_default_value));
+ global::Godot.Rect2 __field_Rect2_default_value = new(new global::Godot.Vector2(10f, 10f), new global::Godot.Vector2(10f, 10f));
+ values.Add(PropertyName.field_Rect2, global::Godot.Variant.From<global::Godot.Rect2>(__field_Rect2_default_value));
+ global::Godot.Rect2I __field_Rect2I_default_value = new(new global::Godot.Vector2I(10, 10), new global::Godot.Vector2I(10, 10));
+ values.Add(PropertyName.field_Rect2I, global::Godot.Variant.From<global::Godot.Rect2I>(__field_Rect2I_default_value));
+ global::Godot.Transform2D __field_Transform2D_default_value = global::Godot.Transform2D.Identity;
+ values.Add(PropertyName.field_Transform2D, global::Godot.Variant.From<global::Godot.Transform2D>(__field_Transform2D_default_value));
+ global::Godot.Vector3 __field_Vector3_default_value = new(10f, 10f, 10f);
+ values.Add(PropertyName.field_Vector3, global::Godot.Variant.From<global::Godot.Vector3>(__field_Vector3_default_value));
+ global::Godot.Vector3I __field_Vector3I_default_value = global::Godot.Vector3I.Back;
+ values.Add(PropertyName.field_Vector3I, global::Godot.Variant.From<global::Godot.Vector3I>(__field_Vector3I_default_value));
+ global::Godot.Basis __field_Basis_default_value = new global::Godot.Basis(global::Godot.Quaternion.Identity);
+ values.Add(PropertyName.field_Basis, global::Godot.Variant.From<global::Godot.Basis>(__field_Basis_default_value));
+ global::Godot.Quaternion __field_Quaternion_default_value = new global::Godot.Quaternion(global::Godot.Basis.Identity);
+ values.Add(PropertyName.field_Quaternion, global::Godot.Variant.From<global::Godot.Quaternion>(__field_Quaternion_default_value));
+ global::Godot.Transform3D __field_Transform3D_default_value = global::Godot.Transform3D.Identity;
+ values.Add(PropertyName.field_Transform3D, global::Godot.Variant.From<global::Godot.Transform3D>(__field_Transform3D_default_value));
+ global::Godot.Vector4 __field_Vector4_default_value = new(10f, 10f, 10f, 10f);
+ values.Add(PropertyName.field_Vector4, global::Godot.Variant.From<global::Godot.Vector4>(__field_Vector4_default_value));
+ global::Godot.Vector4I __field_Vector4I_default_value = global::Godot.Vector4I.One;
+ values.Add(PropertyName.field_Vector4I, global::Godot.Variant.From<global::Godot.Vector4I>(__field_Vector4I_default_value));
+ global::Godot.Projection __field_Projection_default_value = global::Godot.Projection.Identity;
+ values.Add(PropertyName.field_Projection, global::Godot.Variant.From<global::Godot.Projection>(__field_Projection_default_value));
+ global::Godot.Aabb __field_Aabb_default_value = new global::Godot.Aabb(10f, 10f, 10f, new global::Godot.Vector3(1f, 1f, 1f));
+ values.Add(PropertyName.field_Aabb, global::Godot.Variant.From<global::Godot.Aabb>(__field_Aabb_default_value));
+ global::Godot.Color __field_Color_default_value = global::Godot.Colors.Aquamarine;
+ values.Add(PropertyName.field_Color, global::Godot.Variant.From<global::Godot.Color>(__field_Color_default_value));
+ global::Godot.Plane __field_Plane_default_value = global::Godot.Plane.PlaneXZ;
+ values.Add(PropertyName.field_Plane, global::Godot.Variant.From<global::Godot.Plane>(__field_Plane_default_value));
+ global::Godot.Callable __field_Callable_default_value = new global::Godot.Callable(global::Godot.Engine.GetMainLoop(), "_process");
+ values.Add(PropertyName.field_Callable, global::Godot.Variant.From<global::Godot.Callable>(__field_Callable_default_value));
+ global::Godot.Signal __field_Signal_default_value = new global::Godot.Signal(global::Godot.Engine.GetMainLoop(), "property_list_changed");
+ values.Add(PropertyName.field_Signal, global::Godot.Variant.From<global::Godot.Signal>(__field_Signal_default_value));
+ global::ExportedFields.MyEnum __field_Enum_default_value = global::ExportedFields.MyEnum.C;
+ values.Add(PropertyName.field_Enum, global::Godot.Variant.From<global::ExportedFields.MyEnum>(__field_Enum_default_value));
+ global::ExportedFields.MyFlagsEnum __field_FlagsEnum_default_value = global::ExportedFields.MyFlagsEnum.C;
+ values.Add(PropertyName.field_FlagsEnum, global::Godot.Variant.From<global::ExportedFields.MyFlagsEnum>(__field_FlagsEnum_default_value));
+ byte[] __field_ByteArray_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.field_ByteArray, global::Godot.Variant.From<byte[]>(__field_ByteArray_default_value));
+ int[] __field_Int32Array_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.field_Int32Array, global::Godot.Variant.From<int[]>(__field_Int32Array_default_value));
+ long[] __field_Int64Array_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.field_Int64Array, global::Godot.Variant.From<long[]>(__field_Int64Array_default_value));
+ float[] __field_SingleArray_default_value = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ values.Add(PropertyName.field_SingleArray, global::Godot.Variant.From<float[]>(__field_SingleArray_default_value));
+ double[] __field_DoubleArray_default_value = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ values.Add(PropertyName.field_DoubleArray, global::Godot.Variant.From<double[]>(__field_DoubleArray_default_value));
+ string[] __field_StringArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_StringArray, global::Godot.Variant.From<string[]>(__field_StringArray_default_value));
+ string[] __field_StringArrayEnum_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_StringArrayEnum, global::Godot.Variant.From<string[]>(__field_StringArrayEnum_default_value));
+ global::Godot.Vector2[] __field_Vector2Array_default_value = { global::Godot.Vector2.Up, global::Godot.Vector2.Down, global::Godot.Vector2.Left, global::Godot.Vector2.Right };
+ values.Add(PropertyName.field_Vector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(__field_Vector2Array_default_value));
+ global::Godot.Vector3[] __field_Vector3Array_default_value = { global::Godot.Vector3.Up, global::Godot.Vector3.Down, global::Godot.Vector3.Left, global::Godot.Vector3.Right };
+ values.Add(PropertyName.field_Vector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(__field_Vector3Array_default_value));
+ global::Godot.Color[] __field_ColorArray_default_value = { global::Godot.Colors.Aqua, global::Godot.Colors.Aquamarine, global::Godot.Colors.Azure, global::Godot.Colors.Beige };
+ values.Add(PropertyName.field_ColorArray, global::Godot.Variant.From<global::Godot.Color[]>(__field_ColorArray_default_value));
+ global::Godot.GodotObject[] __field_GodotObjectOrDerivedArray_default_value = { null };
+ values.Add(PropertyName.field_GodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(__field_GodotObjectOrDerivedArray_default_value));
+ global::Godot.StringName[] __field_StringNameArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_StringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(__field_StringNameArray_default_value));
+ global::Godot.NodePath[] __field_NodePathArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_NodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(__field_NodePathArray_default_value));
+ global::Godot.Rid[] __field_RidArray_default_value = { default, default, default };
+ values.Add(PropertyName.field_RidArray, global::Godot.Variant.From<global::Godot.Rid[]>(__field_RidArray_default_value));
+ int[] __field_empty_Int32Array_default_value = global::System.Array.Empty<int>();
+ values.Add(PropertyName.field_empty_Int32Array, global::Godot.Variant.From<int[]>(__field_empty_Int32Array_default_value));
+ int[] __field_array_from_list_default_value = new global::System.Collections.Generic.List<int>(global::System.Array.Empty<int>()).ToArray();
+ values.Add(PropertyName.field_array_from_list, global::Godot.Variant.From<int[]>(__field_array_from_list_default_value));
+ global::Godot.Variant __field_Variant_default_value = "foo";
+ values.Add(PropertyName.field_Variant, global::Godot.Variant.From<global::Godot.Variant>(__field_Variant_default_value));
+ global::Godot.GodotObject __field_GodotObjectOrDerived_default_value = default;
+ values.Add(PropertyName.field_GodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(__field_GodotObjectOrDerived_default_value));
+ global::Godot.Texture __field_GodotResourceTexture_default_value = default;
+ values.Add(PropertyName.field_GodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(__field_GodotResourceTexture_default_value));
+ global::Godot.StringName __field_StringName_default_value = new global::Godot.StringName("foo");
+ values.Add(PropertyName.field_StringName, global::Godot.Variant.From<global::Godot.StringName>(__field_StringName_default_value));
+ global::Godot.NodePath __field_NodePath_default_value = new global::Godot.NodePath("foo");
+ values.Add(PropertyName.field_NodePath, global::Godot.Variant.From<global::Godot.NodePath>(__field_NodePath_default_value));
+ global::Godot.Rid __field_Rid_default_value = default;
+ values.Add(PropertyName.field_Rid, global::Godot.Variant.From<global::Godot.Rid>(__field_Rid_default_value));
+ global::Godot.Collections.Dictionary __field_GodotDictionary_default_value = new() { { "foo", 10 }, { global::Godot.Vector2.Up, global::Godot.Colors.Chocolate } };
+ values.Add(PropertyName.field_GodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(__field_GodotDictionary_default_value));
+ global::Godot.Collections.Array __field_GodotArray_default_value = new() { "foo", 10, global::Godot.Vector2.Up, global::Godot.Colors.Chocolate };
+ values.Add(PropertyName.field_GodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(__field_GodotArray_default_value));
+ global::Godot.Collections.Dictionary<string, bool> __field_GodotGenericDictionary_default_value = new() { { "foo", true }, { "bar", false } };
+ values.Add(PropertyName.field_GodotGenericDictionary, global::Godot.Variant.CreateFrom(__field_GodotGenericDictionary_default_value));
+ global::Godot.Collections.Array<int> __field_GodotGenericArray_default_value = new() { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.field_GodotGenericArray, global::Godot.Variant.CreateFrom(__field_GodotGenericArray_default_value));
+ long[] __field_empty_Int64Array_default_value = global::System.Array.Empty<long>();
+ values.Add(PropertyName.field_empty_Int64Array, global::Godot.Variant.From<long[]>(__field_empty_Int64Array_default_value));
+ return values;
+ }
+#endif // TOOLS
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..b5ec9b5b49
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -0,0 +1,933 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportedProperties
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'NotGenerate_Complex_Lamda_Property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName NotGenerate_Complex_Lamda_Property = "NotGenerate_Complex_Lamda_Property";
+ /// <summary>
+ /// Cached name for the 'NotGenerate_Lamda_NoField_Property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName NotGenerate_Lamda_NoField_Property = "NotGenerate_Lamda_NoField_Property";
+ /// <summary>
+ /// Cached name for the 'NotGenerate_Complex_Return_Property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName NotGenerate_Complex_Return_Property = "NotGenerate_Complex_Return_Property";
+ /// <summary>
+ /// Cached name for the 'NotGenerate_Returns_Property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName NotGenerate_Returns_Property = "NotGenerate_Returns_Property";
+ /// <summary>
+ /// Cached name for the 'FullProperty_String' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName FullProperty_String = "FullProperty_String";
+ /// <summary>
+ /// Cached name for the 'FullProperty_String_Complex' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName FullProperty_String_Complex = "FullProperty_String_Complex";
+ /// <summary>
+ /// Cached name for the 'LamdaProperty_String' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName LamdaProperty_String = "LamdaProperty_String";
+ /// <summary>
+ /// Cached name for the 'property_Boolean' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Boolean = "property_Boolean";
+ /// <summary>
+ /// Cached name for the 'property_Char' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Char = "property_Char";
+ /// <summary>
+ /// Cached name for the 'property_SByte' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_SByte = "property_SByte";
+ /// <summary>
+ /// Cached name for the 'property_Int16' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Int16 = "property_Int16";
+ /// <summary>
+ /// Cached name for the 'property_Int32' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Int32 = "property_Int32";
+ /// <summary>
+ /// Cached name for the 'property_Int64' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Int64 = "property_Int64";
+ /// <summary>
+ /// Cached name for the 'property_Byte' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Byte = "property_Byte";
+ /// <summary>
+ /// Cached name for the 'property_UInt16' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_UInt16 = "property_UInt16";
+ /// <summary>
+ /// Cached name for the 'property_UInt32' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_UInt32 = "property_UInt32";
+ /// <summary>
+ /// Cached name for the 'property_UInt64' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_UInt64 = "property_UInt64";
+ /// <summary>
+ /// Cached name for the 'property_Single' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Single = "property_Single";
+ /// <summary>
+ /// Cached name for the 'property_Double' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Double = "property_Double";
+ /// <summary>
+ /// Cached name for the 'property_String' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_String = "property_String";
+ /// <summary>
+ /// Cached name for the 'property_Vector2' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector2 = "property_Vector2";
+ /// <summary>
+ /// Cached name for the 'property_Vector2I' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector2I = "property_Vector2I";
+ /// <summary>
+ /// Cached name for the 'property_Rect2' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Rect2 = "property_Rect2";
+ /// <summary>
+ /// Cached name for the 'property_Rect2I' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Rect2I = "property_Rect2I";
+ /// <summary>
+ /// Cached name for the 'property_Transform2D' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Transform2D = "property_Transform2D";
+ /// <summary>
+ /// Cached name for the 'property_Vector3' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector3 = "property_Vector3";
+ /// <summary>
+ /// Cached name for the 'property_Vector3I' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector3I = "property_Vector3I";
+ /// <summary>
+ /// Cached name for the 'property_Basis' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Basis = "property_Basis";
+ /// <summary>
+ /// Cached name for the 'property_Quaternion' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Quaternion = "property_Quaternion";
+ /// <summary>
+ /// Cached name for the 'property_Transform3D' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Transform3D = "property_Transform3D";
+ /// <summary>
+ /// Cached name for the 'property_Vector4' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector4 = "property_Vector4";
+ /// <summary>
+ /// Cached name for the 'property_Vector4I' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector4I = "property_Vector4I";
+ /// <summary>
+ /// Cached name for the 'property_Projection' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Projection = "property_Projection";
+ /// <summary>
+ /// Cached name for the 'property_Aabb' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Aabb = "property_Aabb";
+ /// <summary>
+ /// Cached name for the 'property_Color' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Color = "property_Color";
+ /// <summary>
+ /// Cached name for the 'property_Plane' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Plane = "property_Plane";
+ /// <summary>
+ /// Cached name for the 'property_Callable' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Callable = "property_Callable";
+ /// <summary>
+ /// Cached name for the 'property_Signal' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Signal = "property_Signal";
+ /// <summary>
+ /// Cached name for the 'property_Enum' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Enum = "property_Enum";
+ /// <summary>
+ /// Cached name for the 'property_FlagsEnum' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_FlagsEnum = "property_FlagsEnum";
+ /// <summary>
+ /// Cached name for the 'property_ByteArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_ByteArray = "property_ByteArray";
+ /// <summary>
+ /// Cached name for the 'property_Int32Array' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Int32Array = "property_Int32Array";
+ /// <summary>
+ /// Cached name for the 'property_Int64Array' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Int64Array = "property_Int64Array";
+ /// <summary>
+ /// Cached name for the 'property_SingleArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_SingleArray = "property_SingleArray";
+ /// <summary>
+ /// Cached name for the 'property_DoubleArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_DoubleArray = "property_DoubleArray";
+ /// <summary>
+ /// Cached name for the 'property_StringArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_StringArray = "property_StringArray";
+ /// <summary>
+ /// Cached name for the 'property_StringArrayEnum' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_StringArrayEnum = "property_StringArrayEnum";
+ /// <summary>
+ /// Cached name for the 'property_Vector2Array' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector2Array = "property_Vector2Array";
+ /// <summary>
+ /// Cached name for the 'property_Vector3Array' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Vector3Array = "property_Vector3Array";
+ /// <summary>
+ /// Cached name for the 'property_ColorArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_ColorArray = "property_ColorArray";
+ /// <summary>
+ /// Cached name for the 'property_GodotObjectOrDerivedArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotObjectOrDerivedArray = "property_GodotObjectOrDerivedArray";
+ /// <summary>
+ /// Cached name for the 'field_StringNameArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_StringNameArray = "field_StringNameArray";
+ /// <summary>
+ /// Cached name for the 'field_NodePathArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_NodePathArray = "field_NodePathArray";
+ /// <summary>
+ /// Cached name for the 'field_RidArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName field_RidArray = "field_RidArray";
+ /// <summary>
+ /// Cached name for the 'property_Variant' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Variant = "property_Variant";
+ /// <summary>
+ /// Cached name for the 'property_GodotObjectOrDerived' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotObjectOrDerived = "property_GodotObjectOrDerived";
+ /// <summary>
+ /// Cached name for the 'property_GodotResourceTexture' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotResourceTexture = "property_GodotResourceTexture";
+ /// <summary>
+ /// Cached name for the 'property_StringName' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_StringName = "property_StringName";
+ /// <summary>
+ /// Cached name for the 'property_NodePath' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_NodePath = "property_NodePath";
+ /// <summary>
+ /// Cached name for the 'property_Rid' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_Rid = "property_Rid";
+ /// <summary>
+ /// Cached name for the 'property_GodotDictionary' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotDictionary = "property_GodotDictionary";
+ /// <summary>
+ /// Cached name for the 'property_GodotArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotArray = "property_GodotArray";
+ /// <summary>
+ /// Cached name for the 'property_GodotGenericDictionary' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotGenericDictionary = "property_GodotGenericDictionary";
+ /// <summary>
+ /// Cached name for the 'property_GodotGenericArray' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName property_GodotGenericArray = "property_GodotGenericArray";
+ /// <summary>
+ /// Cached name for the '_notGenerate_Property_String' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _notGenerate_Property_String = "_notGenerate_Property_String";
+ /// <summary>
+ /// Cached name for the '_notGenerate_Property_Int' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _notGenerate_Property_Int = "_notGenerate_Property_Int";
+ /// <summary>
+ /// Cached name for the '_fullProperty_String' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _fullProperty_String = "_fullProperty_String";
+ /// <summary>
+ /// Cached name for the '_fullProperty_String_Complex' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _fullProperty_String_Complex = "_fullProperty_String_Complex";
+ /// <summary>
+ /// Cached name for the '_lamdaProperty_String' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _lamdaProperty_String = "_lamdaProperty_String";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.NotGenerate_Complex_Lamda_Property) {
+ this.NotGenerate_Complex_Lamda_Property = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Lamda_NoField_Property) {
+ this.NotGenerate_Lamda_NoField_Property = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Complex_Return_Property) {
+ this.NotGenerate_Complex_Return_Property = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Returns_Property) {
+ this.NotGenerate_Returns_Property = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.FullProperty_String) {
+ this.FullProperty_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.FullProperty_String_Complex) {
+ this.FullProperty_String_Complex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.LamdaProperty_String) {
+ this.LamdaProperty_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Boolean) {
+ this.property_Boolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Char) {
+ this.property_Char = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_SByte) {
+ this.property_SByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Int16) {
+ this.property_Int16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Int32) {
+ this.property_Int32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Int64) {
+ this.property_Int64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Byte) {
+ this.property_Byte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt16) {
+ this.property_UInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt32) {
+ this.property_UInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt64) {
+ this.property_UInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Single) {
+ this.property_Single = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Double) {
+ this.property_Double = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_String) {
+ this.property_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2) {
+ this.property_Vector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2I) {
+ this.property_Vector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Rect2) {
+ this.property_Rect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Rect2I) {
+ this.property_Rect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Transform2D) {
+ this.property_Transform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3) {
+ this.property_Vector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3I) {
+ this.property_Vector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Basis) {
+ this.property_Basis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Quaternion) {
+ this.property_Quaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Transform3D) {
+ this.property_Transform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector4) {
+ this.property_Vector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector4I) {
+ this.property_Vector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Projection) {
+ this.property_Projection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Aabb) {
+ this.property_Aabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Color) {
+ this.property_Color = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Plane) {
+ this.property_Plane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Callable) {
+ this.property_Callable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Signal) {
+ this.property_Signal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Enum) {
+ this.property_Enum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyEnum>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_FlagsEnum) {
+ this.property_FlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyFlagsEnum>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_ByteArray) {
+ this.property_ByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Int32Array) {
+ this.property_Int32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Int64Array) {
+ this.property_Int64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_SingleArray) {
+ this.property_SingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_DoubleArray) {
+ this.property_DoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_StringArray) {
+ this.property_StringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_StringArrayEnum) {
+ this.property_StringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2Array) {
+ this.property_Vector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3Array) {
+ this.property_Vector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_ColorArray) {
+ this.property_ColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotObjectOrDerivedArray) {
+ this.property_GodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_StringNameArray) {
+ this.field_StringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePathArray) {
+ this.field_NodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.field_RidArray) {
+ this.field_RidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Variant) {
+ this.property_Variant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotObjectOrDerived) {
+ this.property_GodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotResourceTexture) {
+ this.property_GodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_StringName) {
+ this.property_StringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_NodePath) {
+ this.property_NodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_Rid) {
+ this.property_Rid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotDictionary) {
+ this.property_GodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotArray) {
+ this.property_GodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotGenericDictionary) {
+ this.property_GodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotGenericArray) {
+ this.property_GodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value);
+ return true;
+ }
+ else if (name == PropertyName._notGenerate_Property_String) {
+ this._notGenerate_Property_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName._notGenerate_Property_Int) {
+ this._notGenerate_Property_Int = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
+ return true;
+ }
+ else if (name == PropertyName._fullProperty_String) {
+ this._fullProperty_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName._fullProperty_String_Complex) {
+ this._fullProperty_String_Complex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ else if (name == PropertyName._lamdaProperty_String) {
+ this._lamdaProperty_String = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.NotGenerate_Complex_Lamda_Property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerate_Complex_Lamda_Property);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Lamda_NoField_Property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerate_Lamda_NoField_Property);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Complex_Return_Property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerate_Complex_Return_Property);
+ return true;
+ }
+ else if (name == PropertyName.NotGenerate_Returns_Property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerate_Returns_Property);
+ return true;
+ }
+ else if (name == PropertyName.FullProperty_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.FullProperty_String);
+ return true;
+ }
+ else if (name == PropertyName.FullProperty_String_Complex) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.FullProperty_String_Complex);
+ return true;
+ }
+ else if (name == PropertyName.LamdaProperty_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.LamdaProperty_String);
+ return true;
+ }
+ else if (name == PropertyName.property_Boolean) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.property_Boolean);
+ return true;
+ }
+ else if (name == PropertyName.property_Char) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this.property_Char);
+ return true;
+ }
+ else if (name == PropertyName.property_SByte) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this.property_SByte);
+ return true;
+ }
+ else if (name == PropertyName.property_Int16) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this.property_Int16);
+ return true;
+ }
+ else if (name == PropertyName.property_Int32) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.property_Int32);
+ return true;
+ }
+ else if (name == PropertyName.property_Int64) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this.property_Int64);
+ return true;
+ }
+ else if (name == PropertyName.property_Byte) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this.property_Byte);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt16) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this.property_UInt16);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt32) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this.property_UInt32);
+ return true;
+ }
+ else if (name == PropertyName.property_UInt64) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this.property_UInt64);
+ return true;
+ }
+ else if (name == PropertyName.property_Single) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this.property_Single);
+ return true;
+ }
+ else if (name == PropertyName.property_Double) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this.property_Double);
+ return true;
+ }
+ else if (name == PropertyName.property_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.property_String);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this.property_Vector2);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this.property_Vector2I);
+ return true;
+ }
+ else if (name == PropertyName.property_Rect2) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this.property_Rect2);
+ return true;
+ }
+ else if (name == PropertyName.property_Rect2I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this.property_Rect2I);
+ return true;
+ }
+ else if (name == PropertyName.property_Transform2D) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this.property_Transform2D);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this.property_Vector3);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this.property_Vector3I);
+ return true;
+ }
+ else if (name == PropertyName.property_Basis) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this.property_Basis);
+ return true;
+ }
+ else if (name == PropertyName.property_Quaternion) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this.property_Quaternion);
+ return true;
+ }
+ else if (name == PropertyName.property_Transform3D) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this.property_Transform3D);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector4) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this.property_Vector4);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector4I) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this.property_Vector4I);
+ return true;
+ }
+ else if (name == PropertyName.property_Projection) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this.property_Projection);
+ return true;
+ }
+ else if (name == PropertyName.property_Aabb) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this.property_Aabb);
+ return true;
+ }
+ else if (name == PropertyName.property_Color) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this.property_Color);
+ return true;
+ }
+ else if (name == PropertyName.property_Plane) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this.property_Plane);
+ return true;
+ }
+ else if (name == PropertyName.property_Callable) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.property_Callable);
+ return true;
+ }
+ else if (name == PropertyName.property_Signal) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this.property_Signal);
+ return true;
+ }
+ else if (name == PropertyName.property_Enum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyEnum>(this.property_Enum);
+ return true;
+ }
+ else if (name == PropertyName.property_FlagsEnum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyFlagsEnum>(this.property_FlagsEnum);
+ return true;
+ }
+ else if (name == PropertyName.property_ByteArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this.property_ByteArray);
+ return true;
+ }
+ else if (name == PropertyName.property_Int32Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.property_Int32Array);
+ return true;
+ }
+ else if (name == PropertyName.property_Int64Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.property_Int64Array);
+ return true;
+ }
+ else if (name == PropertyName.property_SingleArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this.property_SingleArray);
+ return true;
+ }
+ else if (name == PropertyName.property_DoubleArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this.property_DoubleArray);
+ return true;
+ }
+ else if (name == PropertyName.property_StringArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.property_StringArray);
+ return true;
+ }
+ else if (name == PropertyName.property_StringArrayEnum) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.property_StringArrayEnum);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector2Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this.property_Vector2Array);
+ return true;
+ }
+ else if (name == PropertyName.property_Vector3Array) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this.property_Vector3Array);
+ return true;
+ }
+ else if (name == PropertyName.property_ColorArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this.property_ColorArray);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotObjectOrDerivedArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this.property_GodotObjectOrDerivedArray);
+ return true;
+ }
+ else if (name == PropertyName.field_StringNameArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this.field_StringNameArray);
+ return true;
+ }
+ else if (name == PropertyName.field_NodePathArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this.field_NodePathArray);
+ return true;
+ }
+ else if (name == PropertyName.field_RidArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this.field_RidArray);
+ return true;
+ }
+ else if (name == PropertyName.property_Variant) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this.property_Variant);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotObjectOrDerived) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this.property_GodotObjectOrDerived);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotResourceTexture) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this.property_GodotResourceTexture);
+ return true;
+ }
+ else if (name == PropertyName.property_StringName) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this.property_StringName);
+ return true;
+ }
+ else if (name == PropertyName.property_NodePath) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.property_NodePath);
+ return true;
+ }
+ else if (name == PropertyName.property_Rid) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this.property_Rid);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotDictionary) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this.property_GodotDictionary);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this.property_GodotArray);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotGenericDictionary) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this.property_GodotGenericDictionary);
+ return true;
+ }
+ else if (name == PropertyName.property_GodotGenericArray) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.property_GodotGenericArray);
+ return true;
+ }
+ else if (name == PropertyName._notGenerate_Property_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._notGenerate_Property_String);
+ return true;
+ }
+ else if (name == PropertyName._notGenerate_Property_Int) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this._notGenerate_Property_Int);
+ return true;
+ }
+ else if (name == PropertyName._fullProperty_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._fullProperty_String);
+ return true;
+ }
+ else if (name == PropertyName._fullProperty_String_Complex) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._fullProperty_String_Complex);
+ return true;
+ }
+ else if (name == PropertyName._lamdaProperty_String) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._lamdaProperty_String);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._notGenerate_Property_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerate_Complex_Lamda_Property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerate_Lamda_NoField_Property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerate_Complex_Return_Property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._notGenerate_Property_Int, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerate_Returns_Property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._fullProperty_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.FullProperty_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._fullProperty_String_Complex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.FullProperty_String_Complex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._lamdaProperty_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.LamdaProperty_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.property_Boolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Char, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_SByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Int16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Int32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Int64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Byte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_UInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_UInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_UInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.property_Single, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.property_Double, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.property_String, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName.property_Vector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName.property_Vector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName.property_Rect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName.property_Rect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName.property_Transform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName.property_Vector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName.property_Vector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName.property_Basis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName.property_Quaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName.property_Transform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName.property_Vector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName.property_Vector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName.property_Projection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName.property_Aabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName.property_Color, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName.property_Plane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.property_Callable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName.property_Signal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_Enum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.property_FlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName.property_ByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.property_Int32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.property_Int64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName.property_SingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName.property_DoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.property_StringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.property_StringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName.property_Vector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName.property_Vector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName.property_ColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.property_GodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_StringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_NodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_RidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName.property_Variant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.property_GodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.property_GodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName.property_StringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.property_NodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.property_Rid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.property_GodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.property_GodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.property_GodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.property_GodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs
new file mode 100644
index 0000000000..a4cd10d080
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs
@@ -0,0 +1,147 @@
+partial class ExportedProperties
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+#if TOOLS
+ /// <summary>
+ /// Get the default values for all properties declared in this class.
+ /// This method is used by Godot to determine the value that will be
+ /// used by the inspector when resetting properties.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues()
+ {
+ var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(64);
+ string __NotGenerate_Complex_Lamda_Property_default_value = default;
+ values.Add(PropertyName.NotGenerate_Complex_Lamda_Property, global::Godot.Variant.From<string>(__NotGenerate_Complex_Lamda_Property_default_value));
+ string __NotGenerate_Lamda_NoField_Property_default_value = default;
+ values.Add(PropertyName.NotGenerate_Lamda_NoField_Property, global::Godot.Variant.From<string>(__NotGenerate_Lamda_NoField_Property_default_value));
+ string __NotGenerate_Complex_Return_Property_default_value = default;
+ values.Add(PropertyName.NotGenerate_Complex_Return_Property, global::Godot.Variant.From<string>(__NotGenerate_Complex_Return_Property_default_value));
+ string __NotGenerate_Returns_Property_default_value = default;
+ values.Add(PropertyName.NotGenerate_Returns_Property, global::Godot.Variant.From<string>(__NotGenerate_Returns_Property_default_value));
+ string __FullProperty_String_default_value = "FullProperty_String";
+ values.Add(PropertyName.FullProperty_String, global::Godot.Variant.From<string>(__FullProperty_String_default_value));
+ string __FullProperty_String_Complex_default_value = new string("FullProperty_String_Complex") + global::System.Convert.ToInt32("1");
+ values.Add(PropertyName.FullProperty_String_Complex, global::Godot.Variant.From<string>(__FullProperty_String_Complex_default_value));
+ string __LamdaProperty_String_default_value = "LamdaProperty_String";
+ values.Add(PropertyName.LamdaProperty_String, global::Godot.Variant.From<string>(__LamdaProperty_String_default_value));
+ bool __property_Boolean_default_value = true;
+ values.Add(PropertyName.property_Boolean, global::Godot.Variant.From<bool>(__property_Boolean_default_value));
+ char __property_Char_default_value = 'f';
+ values.Add(PropertyName.property_Char, global::Godot.Variant.From<char>(__property_Char_default_value));
+ sbyte __property_SByte_default_value = 10;
+ values.Add(PropertyName.property_SByte, global::Godot.Variant.From<sbyte>(__property_SByte_default_value));
+ short __property_Int16_default_value = 10;
+ values.Add(PropertyName.property_Int16, global::Godot.Variant.From<short>(__property_Int16_default_value));
+ int __property_Int32_default_value = 10;
+ values.Add(PropertyName.property_Int32, global::Godot.Variant.From<int>(__property_Int32_default_value));
+ long __property_Int64_default_value = 10;
+ values.Add(PropertyName.property_Int64, global::Godot.Variant.From<long>(__property_Int64_default_value));
+ byte __property_Byte_default_value = 10;
+ values.Add(PropertyName.property_Byte, global::Godot.Variant.From<byte>(__property_Byte_default_value));
+ ushort __property_UInt16_default_value = 10;
+ values.Add(PropertyName.property_UInt16, global::Godot.Variant.From<ushort>(__property_UInt16_default_value));
+ uint __property_UInt32_default_value = 10;
+ values.Add(PropertyName.property_UInt32, global::Godot.Variant.From<uint>(__property_UInt32_default_value));
+ ulong __property_UInt64_default_value = 10;
+ values.Add(PropertyName.property_UInt64, global::Godot.Variant.From<ulong>(__property_UInt64_default_value));
+ float __property_Single_default_value = 10;
+ values.Add(PropertyName.property_Single, global::Godot.Variant.From<float>(__property_Single_default_value));
+ double __property_Double_default_value = 10;
+ values.Add(PropertyName.property_Double, global::Godot.Variant.From<double>(__property_Double_default_value));
+ string __property_String_default_value = "foo";
+ values.Add(PropertyName.property_String, global::Godot.Variant.From<string>(__property_String_default_value));
+ global::Godot.Vector2 __property_Vector2_default_value = new(10f, 10f);
+ values.Add(PropertyName.property_Vector2, global::Godot.Variant.From<global::Godot.Vector2>(__property_Vector2_default_value));
+ global::Godot.Vector2I __property_Vector2I_default_value = global::Godot.Vector2I.Up;
+ values.Add(PropertyName.property_Vector2I, global::Godot.Variant.From<global::Godot.Vector2I>(__property_Vector2I_default_value));
+ global::Godot.Rect2 __property_Rect2_default_value = new(new global::Godot.Vector2(10f, 10f), new global::Godot.Vector2(10f, 10f));
+ values.Add(PropertyName.property_Rect2, global::Godot.Variant.From<global::Godot.Rect2>(__property_Rect2_default_value));
+ global::Godot.Rect2I __property_Rect2I_default_value = new(new global::Godot.Vector2I(10, 10), new global::Godot.Vector2I(10, 10));
+ values.Add(PropertyName.property_Rect2I, global::Godot.Variant.From<global::Godot.Rect2I>(__property_Rect2I_default_value));
+ global::Godot.Transform2D __property_Transform2D_default_value = global::Godot.Transform2D.Identity;
+ values.Add(PropertyName.property_Transform2D, global::Godot.Variant.From<global::Godot.Transform2D>(__property_Transform2D_default_value));
+ global::Godot.Vector3 __property_Vector3_default_value = new(10f, 10f, 10f);
+ values.Add(PropertyName.property_Vector3, global::Godot.Variant.From<global::Godot.Vector3>(__property_Vector3_default_value));
+ global::Godot.Vector3I __property_Vector3I_default_value = global::Godot.Vector3I.Back;
+ values.Add(PropertyName.property_Vector3I, global::Godot.Variant.From<global::Godot.Vector3I>(__property_Vector3I_default_value));
+ global::Godot.Basis __property_Basis_default_value = new global::Godot.Basis(global::Godot.Quaternion.Identity);
+ values.Add(PropertyName.property_Basis, global::Godot.Variant.From<global::Godot.Basis>(__property_Basis_default_value));
+ global::Godot.Quaternion __property_Quaternion_default_value = new global::Godot.Quaternion(global::Godot.Basis.Identity);
+ values.Add(PropertyName.property_Quaternion, global::Godot.Variant.From<global::Godot.Quaternion>(__property_Quaternion_default_value));
+ global::Godot.Transform3D __property_Transform3D_default_value = global::Godot.Transform3D.Identity;
+ values.Add(PropertyName.property_Transform3D, global::Godot.Variant.From<global::Godot.Transform3D>(__property_Transform3D_default_value));
+ global::Godot.Vector4 __property_Vector4_default_value = new(10f, 10f, 10f, 10f);
+ values.Add(PropertyName.property_Vector4, global::Godot.Variant.From<global::Godot.Vector4>(__property_Vector4_default_value));
+ global::Godot.Vector4I __property_Vector4I_default_value = global::Godot.Vector4I.One;
+ values.Add(PropertyName.property_Vector4I, global::Godot.Variant.From<global::Godot.Vector4I>(__property_Vector4I_default_value));
+ global::Godot.Projection __property_Projection_default_value = global::Godot.Projection.Identity;
+ values.Add(PropertyName.property_Projection, global::Godot.Variant.From<global::Godot.Projection>(__property_Projection_default_value));
+ global::Godot.Aabb __property_Aabb_default_value = new global::Godot.Aabb(10f, 10f, 10f, new global::Godot.Vector3(1f, 1f, 1f));
+ values.Add(PropertyName.property_Aabb, global::Godot.Variant.From<global::Godot.Aabb>(__property_Aabb_default_value));
+ global::Godot.Color __property_Color_default_value = global::Godot.Colors.Aquamarine;
+ values.Add(PropertyName.property_Color, global::Godot.Variant.From<global::Godot.Color>(__property_Color_default_value));
+ global::Godot.Plane __property_Plane_default_value = global::Godot.Plane.PlaneXZ;
+ values.Add(PropertyName.property_Plane, global::Godot.Variant.From<global::Godot.Plane>(__property_Plane_default_value));
+ global::Godot.Callable __property_Callable_default_value = new global::Godot.Callable(global::Godot.Engine.GetMainLoop(), "_process");
+ values.Add(PropertyName.property_Callable, global::Godot.Variant.From<global::Godot.Callable>(__property_Callable_default_value));
+ global::Godot.Signal __property_Signal_default_value = new global::Godot.Signal(global::Godot.Engine.GetMainLoop(), "property_list_changed");
+ values.Add(PropertyName.property_Signal, global::Godot.Variant.From<global::Godot.Signal>(__property_Signal_default_value));
+ global::ExportedProperties.MyEnum __property_Enum_default_value = global::ExportedProperties.MyEnum.C;
+ values.Add(PropertyName.property_Enum, global::Godot.Variant.From<global::ExportedProperties.MyEnum>(__property_Enum_default_value));
+ global::ExportedProperties.MyFlagsEnum __property_FlagsEnum_default_value = global::ExportedProperties.MyFlagsEnum.C;
+ values.Add(PropertyName.property_FlagsEnum, global::Godot.Variant.From<global::ExportedProperties.MyFlagsEnum>(__property_FlagsEnum_default_value));
+ byte[] __property_ByteArray_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.property_ByteArray, global::Godot.Variant.From<byte[]>(__property_ByteArray_default_value));
+ int[] __property_Int32Array_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.property_Int32Array, global::Godot.Variant.From<int[]>(__property_Int32Array_default_value));
+ long[] __property_Int64Array_default_value = { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.property_Int64Array, global::Godot.Variant.From<long[]>(__property_Int64Array_default_value));
+ float[] __property_SingleArray_default_value = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ values.Add(PropertyName.property_SingleArray, global::Godot.Variant.From<float[]>(__property_SingleArray_default_value));
+ double[] __property_DoubleArray_default_value = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ values.Add(PropertyName.property_DoubleArray, global::Godot.Variant.From<double[]>(__property_DoubleArray_default_value));
+ string[] __property_StringArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.property_StringArray, global::Godot.Variant.From<string[]>(__property_StringArray_default_value));
+ string[] __property_StringArrayEnum_default_value = { "foo", "bar" };
+ values.Add(PropertyName.property_StringArrayEnum, global::Godot.Variant.From<string[]>(__property_StringArrayEnum_default_value));
+ global::Godot.Vector2[] __property_Vector2Array_default_value = { global::Godot.Vector2.Up, global::Godot.Vector2.Down, global::Godot.Vector2.Left, global::Godot.Vector2.Right };
+ values.Add(PropertyName.property_Vector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(__property_Vector2Array_default_value));
+ global::Godot.Vector3[] __property_Vector3Array_default_value = { global::Godot.Vector3.Up, global::Godot.Vector3.Down, global::Godot.Vector3.Left, global::Godot.Vector3.Right };
+ values.Add(PropertyName.property_Vector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(__property_Vector3Array_default_value));
+ global::Godot.Color[] __property_ColorArray_default_value = { global::Godot.Colors.Aqua, global::Godot.Colors.Aquamarine, global::Godot.Colors.Azure, global::Godot.Colors.Beige };
+ values.Add(PropertyName.property_ColorArray, global::Godot.Variant.From<global::Godot.Color[]>(__property_ColorArray_default_value));
+ global::Godot.GodotObject[] __property_GodotObjectOrDerivedArray_default_value = { null };
+ values.Add(PropertyName.property_GodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(__property_GodotObjectOrDerivedArray_default_value));
+ global::Godot.StringName[] __field_StringNameArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_StringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(__field_StringNameArray_default_value));
+ global::Godot.NodePath[] __field_NodePathArray_default_value = { "foo", "bar" };
+ values.Add(PropertyName.field_NodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(__field_NodePathArray_default_value));
+ global::Godot.Rid[] __field_RidArray_default_value = { default, default, default };
+ values.Add(PropertyName.field_RidArray, global::Godot.Variant.From<global::Godot.Rid[]>(__field_RidArray_default_value));
+ global::Godot.Variant __property_Variant_default_value = "foo";
+ values.Add(PropertyName.property_Variant, global::Godot.Variant.From<global::Godot.Variant>(__property_Variant_default_value));
+ global::Godot.GodotObject __property_GodotObjectOrDerived_default_value = default;
+ values.Add(PropertyName.property_GodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(__property_GodotObjectOrDerived_default_value));
+ global::Godot.Texture __property_GodotResourceTexture_default_value = default;
+ values.Add(PropertyName.property_GodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(__property_GodotResourceTexture_default_value));
+ global::Godot.StringName __property_StringName_default_value = new global::Godot.StringName("foo");
+ values.Add(PropertyName.property_StringName, global::Godot.Variant.From<global::Godot.StringName>(__property_StringName_default_value));
+ global::Godot.NodePath __property_NodePath_default_value = new global::Godot.NodePath("foo");
+ values.Add(PropertyName.property_NodePath, global::Godot.Variant.From<global::Godot.NodePath>(__property_NodePath_default_value));
+ global::Godot.Rid __property_Rid_default_value = default;
+ values.Add(PropertyName.property_Rid, global::Godot.Variant.From<global::Godot.Rid>(__property_Rid_default_value));
+ global::Godot.Collections.Dictionary __property_GodotDictionary_default_value = new() { { "foo", 10 }, { global::Godot.Vector2.Up, global::Godot.Colors.Chocolate } };
+ values.Add(PropertyName.property_GodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(__property_GodotDictionary_default_value));
+ global::Godot.Collections.Array __property_GodotArray_default_value = new() { "foo", 10, global::Godot.Vector2.Up, global::Godot.Colors.Chocolate };
+ values.Add(PropertyName.property_GodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(__property_GodotArray_default_value));
+ global::Godot.Collections.Dictionary<string, bool> __property_GodotGenericDictionary_default_value = new() { { "foo", true }, { "bar", false } };
+ values.Add(PropertyName.property_GodotGenericDictionary, global::Godot.Variant.CreateFrom(__property_GodotGenericDictionary_default_value));
+ global::Godot.Collections.Array<int> __property_GodotGenericArray_default_value = new() { 0, 1, 2, 3, 4, 5, 6 };
+ values.Add(PropertyName.property_GodotGenericArray, global::Godot.Variant.CreateFrom(__property_GodotGenericArray_default_value));
+ return values;
+ }
+#endif // TOOLS
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Foo_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Foo_ScriptPath.generated.cs
new file mode 100644
index 0000000000..9092ad9938
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Foo_ScriptPath.generated.cs
@@ -0,0 +1,5 @@
+using Godot;
+[ScriptPathAttribute("res://Foo.cs")]
+partial class Foo
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs
new file mode 100644
index 0000000000..72c48595a2
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs
@@ -0,0 +1,5 @@
+using Godot;
+[ScriptPathAttribute("res://Generic.cs")]
+partial class Generic
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs
new file mode 100644
index 0000000000..f757497618
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs
@@ -0,0 +1,61 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class Methods
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the methods contained in this class, for fast lookup.
+ /// </summary>
+ public new class MethodName : global::Godot.GodotObject.MethodName {
+ /// <summary>
+ /// Cached name for the 'MethodWithOverload' method.
+ /// </summary>
+ public new static readonly global::Godot.StringName MethodWithOverload = "MethodWithOverload";
+ }
+ /// <summary>
+ /// Get the method information for all the methods declared in this class.
+ /// This method is used by Godot to register the available methods in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList()
+ {
+ var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(3);
+ methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: null, defaultArguments: null));
+ methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "b", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ return methods;
+ }
+#pragma warning restore CS0109
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ if (method == MethodName.MethodWithOverload && args.Count == 0) {
+ MethodWithOverload();
+ ret = default;
+ return true;
+ }
+ if (method == MethodName.MethodWithOverload && args.Count == 1) {
+ MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0]));
+ ret = default;
+ return true;
+ }
+ if (method == MethodName.MethodWithOverload && args.Count == 2) {
+ MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0]), global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[1]));
+ ret = default;
+ return true;
+ }
+ return base.InvokeGodotClassMethod(method, args, out ret);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool HasGodotClassMethod(in godot_string_name method)
+ {
+ if (method == MethodName.MethodWithOverload) {
+ return true;
+ }
+ return base.HasGodotClassMethod(method);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..f812457aa5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs
@@ -0,0 +1,94 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class MixedReadOnlyWriteOnly
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'readonly_auto_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_auto_property = "readonly_auto_property";
+ /// <summary>
+ /// Cached name for the 'readonly_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_property = "readonly_property";
+ /// <summary>
+ /// Cached name for the 'initonly_auto_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName initonly_auto_property = "initonly_auto_property";
+ /// <summary>
+ /// Cached name for the 'writeonly_property' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName writeonly_property = "writeonly_property";
+ /// <summary>
+ /// Cached name for the 'readonly_field' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName readonly_field = "readonly_field";
+ /// <summary>
+ /// Cached name for the 'writeonly_backing_field' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName writeonly_backing_field = "writeonly_backing_field";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.writeonly_property) {
+ this.writeonly_property = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ else if (name == PropertyName.writeonly_backing_field) {
+ this.writeonly_backing_field = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.readonly_auto_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_auto_property);
+ return true;
+ }
+ else if (name == PropertyName.readonly_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_property);
+ return true;
+ }
+ else if (name == PropertyName.initonly_auto_property) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.initonly_auto_property);
+ return true;
+ }
+ else if (name == PropertyName.readonly_field) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.readonly_field);
+ return true;
+ }
+ else if (name == PropertyName.writeonly_backing_field) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.writeonly_backing_field);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_field, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_auto_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.readonly_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.initonly_auto_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.writeonly_backing_field, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.writeonly_property, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs
new file mode 100644
index 0000000000..328107a237
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs
@@ -0,0 +1,52 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial struct OuterClass
+{
+partial class NestedClass
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the methods contained in this class, for fast lookup.
+ /// </summary>
+ public new class MethodName : global::Godot.RefCounted.MethodName {
+ /// <summary>
+ /// Cached name for the '_Get' method.
+ /// </summary>
+ public new static readonly global::Godot.StringName _Get = "_Get";
+ }
+ /// <summary>
+ /// Get the method information for all the methods declared in this class.
+ /// This method is used by Godot to register the available methods in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList()
+ {
+ var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(1);
+ methods.Add(new(name: MethodName._Get, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)131078, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "property", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ return methods;
+ }
+#pragma warning restore CS0109
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ if (method == MethodName._Get && args.Count == 1) {
+ var callRet = _Get(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0]));
+ ret = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(callRet);
+ return true;
+ }
+ return base.InvokeGodotClassMethod(method, args, out ret);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool HasGodotClassMethod(in godot_string_name method)
+ {
+ if (method == MethodName._Get) {
+ return true;
+ }
+ return base.HasGodotClassMethod(method);
+ }
+}
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..79f014a41e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptProperties.generated.cs
@@ -0,0 +1,15 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial struct OuterClass
+{
+partial class NestedClass
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.RefCounted.PropertyName {
+ }
+}
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptSerialization.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptSerialization.generated.cs
new file mode 100644
index 0000000000..4ecce50178
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptSerialization.generated.cs
@@ -0,0 +1,21 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial struct OuterClass
+{
+partial class NestedClass
+{
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)
+ {
+ base.SaveGodotObjectData(info);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)
+ {
+ base.RestoreGodotObjectData(info);
+ }
+}
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs
new file mode 100644
index 0000000000..8656f4617e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs
@@ -0,0 +1,62 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ScriptBoilerplate
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the methods contained in this class, for fast lookup.
+ /// </summary>
+ public new class MethodName : global::Godot.Node.MethodName {
+ /// <summary>
+ /// Cached name for the '_Process' method.
+ /// </summary>
+ public new static readonly global::Godot.StringName _Process = "_Process";
+ /// <summary>
+ /// Cached name for the 'Bazz' method.
+ /// </summary>
+ public new static readonly global::Godot.StringName Bazz = "Bazz";
+ }
+ /// <summary>
+ /// Get the method information for all the methods declared in this class.
+ /// This method is used by Godot to register the available methods in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList()
+ {
+ var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(2);
+ methods.Add(new(name: MethodName._Process, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)3, name: "delta", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ methods.Add(new(name: MethodName.Bazz, returnVal: new(type: (global::Godot.Variant.Type)2, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "name", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null));
+ return methods;
+ }
+#pragma warning restore CS0109
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ if (method == MethodName._Process && args.Count == 1) {
+ _Process(global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(args[0]));
+ ret = default;
+ return true;
+ }
+ if (method == MethodName.Bazz && args.Count == 1) {
+ var callRet = Bazz(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0]));
+ ret = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(callRet);
+ return true;
+ }
+ return base.InvokeGodotClassMethod(method, args, out ret);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool HasGodotClassMethod(in godot_string_name method)
+ {
+ if (method == MethodName._Process) {
+ return true;
+ }
+ else if (method == MethodName.Bazz) {
+ return true;
+ }
+ return base.HasGodotClassMethod(method);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptPath.generated.cs
new file mode 100644
index 0000000000..ffcd29f7cd
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptPath.generated.cs
@@ -0,0 +1,5 @@
+using Godot;
+[ScriptPathAttribute("res://ScriptBoilerplate.cs")]
+partial class ScriptBoilerplate
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..09368b7ab6
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs
@@ -0,0 +1,62 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ScriptBoilerplate
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.Node.PropertyName {
+ /// <summary>
+ /// Cached name for the '_nodePath' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _nodePath = "_nodePath";
+ /// <summary>
+ /// Cached name for the '_velocity' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName _velocity = "_velocity";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName._nodePath) {
+ this._nodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value);
+ return true;
+ }
+ else if (name == PropertyName._velocity) {
+ this._velocity = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName._nodePath) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this._nodePath);
+ return true;
+ }
+ else if (name == PropertyName._velocity) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this._velocity);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName._nodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._velocity, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs
new file mode 100644
index 0000000000..28bc863b0a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs
@@ -0,0 +1,24 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ScriptBoilerplate
+{
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)
+ {
+ base.SaveGodotObjectData(info);
+ info.AddProperty(PropertyName._nodePath, global::Godot.Variant.From<global::Godot.NodePath>(this._nodePath));
+ info.AddProperty(PropertyName._velocity, global::Godot.Variant.From<int>(this._velocity));
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)
+ {
+ base.RestoreGodotObjectData(info);
+ if (info.TryGetProperty(PropertyName._nodePath, out var _value__nodePath))
+ this._nodePath = _value__nodePath.As<global::Godot.NodePath>();
+ if (info.TryGetProperty(PropertyName._velocity, out var _value__velocity))
+ this._velocity = _value__velocity.As<int>();
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllReadOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllReadOnly.cs
new file mode 100644
index 0000000000..94c2bda363
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllReadOnly.cs
@@ -0,0 +1,9 @@
+using Godot;
+
+public partial class AllReadOnly : GodotObject
+{
+ public readonly string readonly_field = "foo";
+ public string readonly_auto_property { get; } = "foo";
+ public string readonly_property { get => "foo"; }
+ public string initonly_auto_property { get; init; }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllWriteOnly.cs
new file mode 100644
index 0000000000..156d6bb6a5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AllWriteOnly.cs
@@ -0,0 +1,7 @@
+using Godot;
+
+public partial class AllWriteOnly : GodotObject
+{
+ bool writeonly_backing_field = false;
+ public bool writeonly_property { set => writeonly_backing_field = value; }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Bar.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Bar.cs
new file mode 100644
index 0000000000..dfe2217c26
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Bar.cs
@@ -0,0 +1,14 @@
+using Godot;
+
+partial class Bar : GodotObject
+{
+}
+
+// Foo in another file
+partial class Foo
+{
+}
+
+partial class NotSameNameAsFile : GodotObject
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs
new file mode 100644
index 0000000000..160c5d193d
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs
@@ -0,0 +1,7 @@
+using Godot;
+
+public partial class EventSignals : GodotObject
+{
+ [Signal]
+ public delegate void MySignalEventHandler(string str, int num);
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedFields.cs
new file mode 100644
index 0000000000..09d654ffcb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedFields.cs
@@ -0,0 +1,102 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+
+public partial class ExportedFields : GodotObject
+{
+ [Export] private Boolean field_Boolean = true;
+ [Export] private Char field_Char = 'f';
+ [Export] private SByte field_SByte = 10;
+ [Export] private Int16 field_Int16 = 10;
+ [Export] private Int32 field_Int32 = 10;
+ [Export] private Int64 field_Int64 = 10;
+ [Export] private Byte field_Byte = 10;
+ [Export] private UInt16 field_UInt16 = 10;
+ [Export] private UInt32 field_UInt32 = 10;
+ [Export] private UInt64 field_UInt64 = 10;
+ [Export] private Single field_Single = 10;
+ [Export] private Double field_Double = 10;
+ [Export] private String field_String = "foo";
+
+ // Godot structs
+ [Export] private Vector2 field_Vector2 = new(10f, 10f);
+ [Export] private Vector2I field_Vector2I = Vector2I.Up;
+ [Export] private Rect2 field_Rect2 = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
+ [Export] private Rect2I field_Rect2I = new(new Vector2I(10, 10), new Vector2I(10, 10));
+ [Export] private Transform2D field_Transform2D = Transform2D.Identity;
+ [Export] private Vector3 field_Vector3 = new(10f, 10f, 10f);
+ [Export] private Vector3I field_Vector3I = Vector3I.Back;
+ [Export] private Basis field_Basis = new Basis(Quaternion.Identity);
+ [Export] private Quaternion field_Quaternion = new Quaternion(Basis.Identity);
+ [Export] private Transform3D field_Transform3D = Transform3D.Identity;
+ [Export] private Vector4 field_Vector4 = new(10f, 10f, 10f, 10f);
+ [Export] private Vector4I field_Vector4I = Vector4I.One;
+ [Export] private Projection field_Projection = Projection.Identity;
+ [Export] private Aabb field_Aabb = new Aabb(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
+ [Export] private Color field_Color = Colors.Aquamarine;
+ [Export] private Plane field_Plane = Plane.PlaneXZ;
+ [Export] private Callable field_Callable = new Callable(Engine.GetMainLoop(), "_process");
+ [Export] private Signal field_Signal = new Signal(Engine.GetMainLoop(), "property_list_changed");
+
+ // Enums
+ enum MyEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyEnum field_Enum = MyEnum.C;
+
+ [Flags]
+ enum MyFlagsEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyFlagsEnum field_FlagsEnum = MyFlagsEnum.C;
+
+ // Arrays
+ [Export] private Byte[] field_ByteArray = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int32[] field_Int32Array = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int64[] field_Int64Array = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Single[] field_SingleArray = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ [Export] private Double[] field_DoubleArray = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ [Export] private String[] field_StringArray = { "foo", "bar" };
+ [Export(PropertyHint.Enum, "A,B,C")] private String[] field_StringArrayEnum = { "foo", "bar" };
+ [Export] private Vector2[] field_Vector2Array = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
+ [Export] private Vector3[] field_Vector3Array = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
+ [Export] private Color[] field_ColorArray = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
+ [Export] private GodotObject[] field_GodotObjectOrDerivedArray = { null };
+ [Export] private StringName[] field_StringNameArray = { "foo", "bar" };
+ [Export] private NodePath[] field_NodePathArray = { "foo", "bar" };
+ [Export] private Rid[] field_RidArray = { default, default, default };
+ // Note we use Array and not System.Array. This tests the generated namespace qualification.
+ [Export] private Int32[] field_empty_Int32Array = Array.Empty<Int32>();
+ // Note we use List and not System.Collections.Generic.
+ [Export] private int[] field_array_from_list = new List<int>(Array.Empty<int>()).ToArray();
+
+ // Variant
+ [Export] private Variant field_Variant = "foo";
+
+ // Classes
+ [Export] private GodotObject field_GodotObjectOrDerived;
+ [Export] private Godot.Texture field_GodotResourceTexture;
+ [Export] private StringName field_StringName = new StringName("foo");
+ [Export] private NodePath field_NodePath = new NodePath("foo");
+ [Export] private Rid field_Rid;
+
+ [Export]
+ private Godot.Collections.Dictionary field_GodotDictionary = new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export]
+ private Godot.Collections.Array field_GodotArray = new() { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export]
+ private Godot.Collections.Dictionary<string, bool> field_GodotGenericDictionary = new() { { "foo", true }, { "bar", false } };
+
+ [Export]
+ private Godot.Collections.Array<int> field_GodotGenericArray = new() { 0, 1, 2, 3, 4, 5, 6 };
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties.cs
new file mode 100644
index 0000000000..3783838dae
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties.cs
@@ -0,0 +1,186 @@
+using Godot;
+using System;
+
+public partial class ExportedProperties : GodotObject
+{
+ // Do not generate default value
+ private String _notGenerate_Property_String = new string("not generate");
+ [Export]
+ public String NotGenerate_Complex_Lamda_Property
+ {
+ get => _notGenerate_Property_String + Convert.ToInt32("1");
+ set => _notGenerate_Property_String = value;
+ }
+
+ [Export]
+ public String NotGenerate_Lamda_NoField_Property
+ {
+ get => new string("not generate");
+ set => _notGenerate_Property_String = value;
+ }
+
+ [Export]
+ public String NotGenerate_Complex_Return_Property
+ {
+ get
+ {
+ return _notGenerate_Property_String + Convert.ToInt32("1");
+ }
+ set
+ {
+ _notGenerate_Property_String = value;
+ }
+ }
+
+ private int _notGenerate_Property_Int = 1;
+ [Export]
+ public string NotGenerate_Returns_Property
+ {
+ get
+ {
+ if (_notGenerate_Property_Int == 1)
+ {
+ return "a";
+ }
+ else
+ {
+ return "b";
+ }
+ }
+ set
+ {
+ _notGenerate_Property_Int = value == "a" ? 1 : 2;
+ }
+ }
+
+ // Full Property
+ private String _fullProperty_String = "FullProperty_String";
+ [Export]
+ public String FullProperty_String
+ {
+ get
+ {
+ return _fullProperty_String;
+ }
+ set
+ {
+ _fullProperty_String = value;
+ }
+ }
+
+ private String _fullProperty_String_Complex = new string("FullProperty_String_Complex") + Convert.ToInt32("1");
+ [Export]
+ public String FullProperty_String_Complex
+ {
+ get
+ {
+ return _fullProperty_String_Complex;
+ }
+ set
+ {
+ _fullProperty_String_Complex = value;
+ }
+ }
+
+ // Lambda Property
+ private String _lamdaProperty_String = "LamdaProperty_String";
+ [Export]
+ public String LamdaProperty_String
+ {
+ get => _lamdaProperty_String;
+ set => _lamdaProperty_String = value;
+ }
+
+ // Auto Property
+ [Export] private Boolean property_Boolean { get; set; } = true;
+ [Export] private Char property_Char { get; set; } = 'f';
+ [Export] private SByte property_SByte { get; set; } = 10;
+ [Export] private Int16 property_Int16 { get; set; } = 10;
+ [Export] private Int32 property_Int32 { get; set; } = 10;
+ [Export] private Int64 property_Int64 { get; set; } = 10;
+ [Export] private Byte property_Byte { get; set; } = 10;
+ [Export] private UInt16 property_UInt16 { get; set; } = 10;
+ [Export] private UInt32 property_UInt32 { get; set; } = 10;
+ [Export] private UInt64 property_UInt64 { get; set; } = 10;
+ [Export] private Single property_Single { get; set; } = 10;
+ [Export] private Double property_Double { get; set; } = 10;
+ [Export] private String property_String { get; set; } = "foo";
+
+ // Godot structs
+ [Export] private Vector2 property_Vector2 { get; set; } = new(10f, 10f);
+ [Export] private Vector2I property_Vector2I { get; set; } = Vector2I.Up;
+ [Export] private Rect2 property_Rect2 { get; set; } = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
+ [Export] private Rect2I property_Rect2I { get; set; } = new(new Vector2I(10, 10), new Vector2I(10, 10));
+ [Export] private Transform2D property_Transform2D { get; set; } = Transform2D.Identity;
+ [Export] private Vector3 property_Vector3 { get; set; } = new(10f, 10f, 10f);
+ [Export] private Vector3I property_Vector3I { get; set; } = Vector3I.Back;
+ [Export] private Basis property_Basis { get; set; } = new Basis(Quaternion.Identity);
+ [Export] private Quaternion property_Quaternion { get; set; } = new Quaternion(Basis.Identity);
+ [Export] private Transform3D property_Transform3D { get; set; } = Transform3D.Identity;
+ [Export] private Vector4 property_Vector4 { get; set; } = new(10f, 10f, 10f, 10f);
+ [Export] private Vector4I property_Vector4I { get; set; } = Vector4I.One;
+ [Export] private Projection property_Projection { get; set; } = Projection.Identity;
+ [Export] private Aabb property_Aabb { get; set; } = new Aabb(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
+ [Export] private Color property_Color { get; set; } = Colors.Aquamarine;
+ [Export] private Plane property_Plane { get; set; } = Plane.PlaneXZ;
+ [Export] private Callable property_Callable { get; set; } = new Callable(Engine.GetMainLoop(), "_process");
+ [Export] private Signal property_Signal { get; set; } = new Signal(Engine.GetMainLoop(), "property_list_changed");
+
+ // Enums
+ enum MyEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyEnum property_Enum { get; set; } = MyEnum.C;
+
+ [Flags]
+ enum MyFlagsEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyFlagsEnum property_FlagsEnum { get; set; } = MyFlagsEnum.C;
+
+ // Arrays
+ [Export] private Byte[] property_ByteArray { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int32[] property_Int32Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int64[] property_Int64Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Single[] property_SingleArray { get; set; } = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ [Export] private Double[] property_DoubleArray { get; set; } = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ [Export] private String[] property_StringArray { get; set; } = { "foo", "bar" };
+ [Export(PropertyHint.Enum, "A,B,C")] private String[] property_StringArrayEnum { get; set; } = { "foo", "bar" };
+ [Export] private Vector2[] property_Vector2Array { get; set; } = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
+ [Export] private Vector3[] property_Vector3Array { get; set; } = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
+ [Export] private Color[] property_ColorArray { get; set; } = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
+ [Export] private GodotObject[] property_GodotObjectOrDerivedArray { get; set; } = { null };
+ [Export] private StringName[] field_StringNameArray { get; set; } = { "foo", "bar" };
+ [Export] private NodePath[] field_NodePathArray { get; set; } = { "foo", "bar" };
+ [Export] private Rid[] field_RidArray { get; set; } = { default, default, default };
+
+ // Variant
+ [Export] private Variant property_Variant { get; set; } = "foo";
+
+ // Classes
+ [Export] private GodotObject property_GodotObjectOrDerived { get; set; }
+ [Export] private Godot.Texture property_GodotResourceTexture { get; set; }
+ [Export] private StringName property_StringName { get; set; } = new StringName("foo");
+ [Export] private NodePath property_NodePath { get; set; } = new NodePath("foo");
+ [Export] private Rid property_Rid { get; set; }
+
+ [Export]
+ private Godot.Collections.Dictionary property_GodotDictionary { get; set; } = new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export]
+ private Godot.Collections.Array property_GodotArray { get; set; } = new() { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export]
+ private Godot.Collections.Dictionary<string, bool> property_GodotGenericDictionary { get; set; } = new() { { "foo", true }, { "bar", false } };
+
+ [Export]
+ private Godot.Collections.Array<int> property_GodotGenericArray { get; set; } = new() { 0, 1, 2, 3, 4, 5, 6 };
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Foo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Foo.cs
new file mode 100644
index 0000000000..26853553c7
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Foo.cs
@@ -0,0 +1,10 @@
+using Godot;
+
+partial class Foo : GodotObject
+{
+}
+
+// Foo again in the same file
+partial class Foo
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs
new file mode 100644
index 0000000000..84d1ede065
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs
@@ -0,0 +1,18 @@
+using Godot;
+
+partial class Generic<T> : GodotObject
+{
+ private int _field;
+}
+
+// Generic again but different generic parameters
+partial class Generic<T, R> : GodotObject
+{
+ private int _field;
+}
+
+// Generic again but without generic parameters
+partial class Generic : GodotObject
+{
+ private int _field;
+}
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/Methods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Methods.cs
new file mode 100644
index 0000000000..1da9db8204
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Methods.cs
@@ -0,0 +1,26 @@
+using Godot;
+
+public partial class Methods : GodotObject
+{
+ private void MethodWithOverload()
+ {
+ }
+
+ private void MethodWithOverload(int a)
+ {
+ }
+
+ private void MethodWithOverload(int a, int b)
+ {
+ }
+
+ // Should be ignored. The previous one is picked.
+ private void MethodWithOverload(float a, float b)
+ {
+ }
+
+ // Generic methods should be ignored.
+ private void GenericMethod<T>(T t)
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MixedReadOnlyWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MixedReadOnlyWriteOnly.cs
new file mode 100644
index 0000000000..61a48cefc9
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MixedReadOnlyWriteOnly.cs
@@ -0,0 +1,12 @@
+using Godot;
+
+public partial class MixedReadOnlyWriteOnly : GodotObject
+{
+ public readonly string readonly_field = "foo";
+ public string readonly_auto_property { get; } = "foo";
+ public string readonly_property { get => "foo"; }
+ public string initonly_auto_property { get; init; }
+
+ bool writeonly_backing_field = false;
+ public bool writeonly_property { set => writeonly_backing_field = value; }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MoreExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MoreExportedFields.cs
new file mode 100644
index 0000000000..47063a9cdf
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MoreExportedFields.cs
@@ -0,0 +1,8 @@
+using Godot;
+using System;
+
+public partial class ExportedFields : GodotObject
+{
+ // Note we use Array and not System.Array. This tests the generated namespace qualification.
+ [Export] private Int64[] field_empty_Int64Array = Array.Empty<Int64>();
+}
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.Tests/TestData/Sources/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ScriptBoilerplate.cs
new file mode 100644
index 0000000000..5506465b92
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ScriptBoilerplate.cs
@@ -0,0 +1,33 @@
+using Godot;
+
+public partial class ScriptBoilerplate : Node
+{
+ private NodePath _nodePath;
+ private int _velocity;
+
+ public override void _Process(double delta)
+ {
+ _ = delta;
+
+ base._Process(delta);
+ }
+
+ public int Bazz(StringName name)
+ {
+ _ = name;
+ return 1;
+ }
+
+ public void IgnoreThisMethodWithByRefParams(ref int a)
+ {
+ _ = a;
+ }
+}
+
+partial struct OuterClass
+{
+ public partial class NestedClass : RefCounted
+ {
+ public override Variant _Get(StringName property) => default;
+ }
+}
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 23879e0e53..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>
@@ -9,7 +9,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.SourceGenerators</PackageId>
- <Version>4.2.0</Version>
+ <Version>4.3.0</Version>
<PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
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/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/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
index b23b3f42ef..93b5a6c7f8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
@@ -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/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
index b16adb6f55..e516b4dd18 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -195,7 +195,7 @@ namespace GodotTools.Export
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 = "10.0"; // TODO: Turn this hard-coded version into an exporter setting
+ 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");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 91e5118990..c634d9c1ac 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -194,7 +194,7 @@ namespace GodotTools.Export
BundleOutputs = false,
IncludeDebugSymbols = publishConfig.IncludeDebugSymbols,
RidOS = OS.DotNetOS.iOSSimulator,
- UseTempDir = true,
+ UseTempDir = false,
});
}
@@ -361,7 +361,7 @@ namespace GodotTools.Export
}
var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
- $"{GodotSharpDirs.ProjectAssemblyName}.xcframework");
+ $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 650afb4cdf..5408b9b13e 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; }
@@ -52,6 +54,8 @@ namespace GodotTools
public bool SkipBuildBeforePlaying { get; set; } = false;
+ public DateTime LastValidBuildDateTime { get; private set; }
+
[UsedImplicitly]
private bool CreateProjectSolutionIfNeeded()
{
@@ -437,6 +441,21 @@ namespace GodotTools
}
}
+ private void UpdateLastValidBuildDateTime()
+ {
+ var dllName = $"{GodotSharpDirs.ProjectAssemblyName}.dll";
+ var path = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "Debug", dllName);
+ LastValidBuildDateTime = File.GetLastWriteTime(path);
+ }
+
+ private void BuildFinished(BuildResult buildResult)
+ {
+ if (buildResult == BuildResult.Success)
+ {
+ UpdateLastValidBuildDateTime();
+ }
+ }
+
private void BuildStateChanged()
{
if (_bottomPanelBtn != null)
@@ -447,6 +466,8 @@ namespace GodotTools
{
base._EnablePlugin();
+ UpdateLastValidBuildDateTime();
+
ProjectSettings.SettingsChanged += GodotSharpDirs.DetermineProjectLocation;
if (Instance != null)
@@ -615,6 +636,12 @@ namespace GodotTools
AddExportPlugin(exportPlugin);
_exportPluginWeak = WeakRef(exportPlugin);
+ // Inspector plugin
+ var inspectorPlugin = new InspectorPlugin();
+ AddInspectorPlugin(inspectorPlugin);
+ _inspectorPluginWeak = WeakRef(inspectorPlugin);
+ BuildManager.BuildFinished += BuildFinished;
+
BuildManager.Initialize();
RiderPathManager.Initialize();
@@ -627,6 +654,10 @@ namespace GodotTools
base._DisablePlugin();
_editorSettings.SettingsChanged -= OnSettingsChanged;
+
+ // Custom signals aren't automatically disconnected currently.
+ MSBuildPanel.BuildStateChanged -= BuildStateChanged;
+ BuildManager.BuildFinished -= BuildFinished;
}
public override void _ExitTree()
@@ -661,6 +692,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/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..8282ca6ea6
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using Godot;
+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;
+
+ if (File.GetLastWriteTime(script.ResourcePath) > GodotSharpEditor.Instance.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/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 36fdda4625..675651ccf7 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -69,6 +69,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK
#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK
+#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK
#define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK
#define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK
#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK
@@ -1620,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.
@@ -2236,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()) {
@@ -2254,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) {
@@ -2591,7 +2594,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
// Generate icall function
r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " "
- << icall_method << "(" << c_func_sig.as_string() << ") " OPEN_BLOCK;
+ << icall_method << "(" << c_func_sig.as_string() << ")\n" OPEN_BLOCK_L1;
+
+ if (!p_icall.is_static) {
+ r_output << INDENT2 "ExceptionUtils.ThrowIfNullPtr(" CS_PARAM_INSTANCE ");\n";
+ }
if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) {
String ptrcall_return_type;
@@ -2619,11 +2626,6 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n";
}
- if (!p_icall.is_static) {
- r_output << INDENT2 "if (" CS_PARAM_INSTANCE " == IntPtr.Zero)\n"
- << INDENT3 "throw new ArgumentNullException(nameof(" CS_PARAM_INSTANCE "));\n";
- }
-
String argc_str = itos(p_icall.get_arguments_count());
auto generate_call_and_return_stmts = [&](const char *base_indent) {
@@ -2714,7 +2716,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
r_output << c_in_statements.as_string();
- r_output << INDENT3 "for (int i = 0; i < vararg_length; i++) " OPEN_BLOCK
+ r_output << INDENT3 "for (int i = 0; i < vararg_length; i++)\n" OPEN_BLOCK_L3
<< INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n"
<< INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n"
<< CLOSE_BLOCK_L3;
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/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 cc99225a33..63af6ee6e8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -95,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>
@@ -689,7 +692,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the AABB and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Aabb other && Equals(other);
}
@@ -739,7 +742,7 @@ namespace Godot
/// Converts this <see cref="Aabb"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this AABB.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 13c0cde1ef..fa74d5e101 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -6,6 +6,8 @@ using System.Linq;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot.Collections
{
/// <summary>
@@ -14,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,
@@ -22,7 +26,7 @@ namespace Godot.Collections
{
internal godot_array.movable NativeValue;
- private WeakReference<IDisposable> _weakReferenceToSelf;
+ private WeakReference<IDisposable>? _weakReferenceToSelf;
/// <summary>
/// Constructs a new empty <see cref="Array"/>.
@@ -1140,7 +1144,8 @@ 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>
- public static explicit operator Array(Array<T> from)
+ [return: NotNullIfNotNull("from")]
+ public static explicit operator Array?(Array<T>? from)
{
return from?._underlyingArray;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index b4f7b82f60..589d6596f0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -1,7 +1,10 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.ComponentModel;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -1090,7 +1093,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the basis matrix and the object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Basis other && Equals(other);
}
@@ -1140,7 +1143,7 @@ namespace Godot
/// Converts this <see cref="Basis"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this basis.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"[X: {X.ToString(format)}, Y: {Y.ToString(format)}, Z: {Z.ToString(format)}]";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 80c26e5708..968afa664b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -194,7 +194,7 @@ namespace Godot.Bridge
var native = GodotObject.InternalGetClassNativeBase(scriptType);
- var field = native?.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static |
+ var field = native.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic);
if (field == null)
@@ -253,11 +253,15 @@ namespace Godot.Bridge
{
var editorAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor");
- wrapperType = editorAssembly?.GetType("Godot." + nativeTypeNameStr);
- if (wrapperType == null)
+ if (editorAssembly != null)
{
- wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
+ wrapperType = editorAssembly.GetType("Godot." + nativeTypeNameStr);
+
+ if (wrapperType == null)
+ {
+ wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
+ }
}
}
@@ -658,6 +662,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)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index 293e680067..772209064c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -1,7 +1,10 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -1274,7 +1277,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the color and the other object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Color other && Equals(other);
}
@@ -1324,7 +1327,7 @@ namespace Godot
/// Converts this <see cref="Color"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this color.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({R.ToString(format)}, {G.ToString(format)}, {B.ToString(format)}, {A.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index 6c2fb7374c..ab2e0a78b9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -414,7 +414,7 @@ namespace Godot
{
ulong objectId = reader.ReadUInt64();
// ReSharper disable once RedundantNameQualifier
- GodotObject godotObject = GodotObject.InstanceFromId(objectId);
+ GodotObject? godotObject = GodotObject.InstanceFromId(objectId);
if (godotObject == null)
return false;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 2a72ebc32b..d08fad90db 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -5,6 +5,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot.Collections
{
/// <summary>
@@ -19,7 +21,7 @@ namespace Godot.Collections
{
internal godot_dictionary.movable NativeValue;
- private WeakReference<IDisposable> _weakReferenceToSelf;
+ private WeakReference<IDisposable>? _weakReferenceToSelf;
/// <summary>
/// Constructs a new empty <see cref="Dictionary"/>.
@@ -559,7 +561,8 @@ 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>
- public static explicit operator Dictionary(Dictionary<TKey, TValue> 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/Extensions/GodotObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
index 6c90c17078..563a6abe9b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
@@ -1,6 +1,8 @@
using System;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
public partial class GodotObject
@@ -26,7 +28,7 @@ namespace Godot
/// </example>
/// <param name="instanceId">Instance ID of the Object to retrieve.</param>
/// <returns>The <see cref="GodotObject"/> instance.</returns>
- public static GodotObject InstanceFromId(ulong instanceId)
+ public static GodotObject? InstanceFromId(ulong instanceId)
{
return InteropUtils.UnmanagedGetManaged(NativeFuncs.godotsharp_instance_from_id(instanceId));
}
@@ -49,7 +51,7 @@ namespace Godot
/// </summary>
/// <param name="instance">The instance to check.</param>
/// <returns>If the instance is a valid object.</returns>
- public static bool IsInstanceValid(GodotObject instance)
+ public static bool IsInstanceValid(GodotObject? instance)
{
return instance != null && instance.NativeInstance != IntPtr.Zero;
}
@@ -66,9 +68,9 @@ namespace Godot
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
- /// The <see cref="WeakRef"/> reference to the object or <see langword="null"/>.
+ /// The <see cref="Godot.WeakRef"/> reference to the object or <see langword="null"/>.
/// </returns>
- public static WeakRef WeakRef(GodotObject obj)
+ public static WeakRef? WeakRef(GodotObject? obj)
{
if (!IsInstanceValid(obj))
return null;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
index 43598ca84d..84b2a04276 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
@@ -1,8 +1,11 @@
using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using Godot.Bridge;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
public partial class GodotObject : IDisposable
@@ -13,7 +16,7 @@ namespace Godot
internal IntPtr NativePtr;
private bool _memoryOwn;
- private WeakReference<GodotObject> _weakReferenceToSelf;
+ private WeakReference<GodotObject>? _weakReferenceToSelf;
/// <summary>
/// Constructs a new <see cref="GodotObject"/>.
@@ -35,6 +38,8 @@ namespace Godot
{
if (NativePtr == IntPtr.Zero)
{
+ Debug.Assert(nativeCtor != null);
+
NativePtr = nativeCtor();
InteropUtils.TieManagedToUnmanaged(this, NativePtr,
@@ -59,7 +64,7 @@ namespace Godot
/// </summary>
public IntPtr NativeInstance => NativePtr;
- internal static IntPtr GetPtr(GodotObject instance)
+ internal static IntPtr GetPtr(GodotObject? instance)
{
if (instance == null)
return IntPtr.Zero;
@@ -105,7 +110,7 @@ namespace Godot
if (gcHandleToFree != IntPtr.Zero)
{
- object target = GCHandle.FromIntPtr(gcHandleToFree).Target;
+ object? target = GCHandle.FromIntPtr(gcHandleToFree).Target;
// The GC handle may have been replaced in another thread. Release it only if
// it's associated to this managed instance, or if the target is no longer alive.
if (target != this && target != null)
@@ -176,18 +181,14 @@ namespace Godot
internal static Type InternalGetClassNativeBase(Type t)
{
- do
- {
- var assemblyName = t.Assembly.GetName();
+ var name = t.Assembly.GetName().Name;
- if (assemblyName.Name == "GodotSharp")
- return t;
+ if (name == "GodotSharp" || name == "GodotSharpEditor")
+ return t;
- if (assemblyName.Name == "GodotSharpEditor")
- return t;
- } while ((t = t.BaseType) != null);
+ Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
- return null;
+ return InternalGetClassNativeBase(t.BaseType);
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index dc53e48bd0..04b6c2e743 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Text;
#nullable enable
@@ -239,5 +240,13 @@ namespace Godot.NativeInterop
return variant->Type.ToString();
}
+
+ internal static void ThrowIfNullPtr(IntPtr ptr, [CallerArgumentExpression("ptr")] string? paramName = null)
+ {
+ if (ptr == IntPtr.Zero)
+ {
+ throw new ArgumentNullException(paramName);
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index d5d9404ed1..c806263edb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -474,7 +474,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 +725,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 +875,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 +939,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 +971,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 +1003,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 +1035,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 +1067,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 +1099,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 +1131,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/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 93a83b701b..0cc89d78af 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -259,7 +259,7 @@ namespace Godot.NativeInterop
}
return new godot_callable(method /* Takes ownership of disposable */,
- p_managed_callable.Target.GetInstanceId());
+ p_managed_callable.Target?.GetInstanceId() ?? 0);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index f216fb7ea3..0af640533d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -39,11 +42,11 @@ namespace Godot
/// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene.
/// </code>
/// </example>
- public sealed class NodePath : IDisposable, IEquatable<NodePath>
+ public sealed class NodePath : IDisposable, IEquatable<NodePath?>
{
internal godot_node_path.movable NativeValue;
- private WeakReference<IDisposable> _weakReferenceToSelf;
+ private WeakReference<IDisposable>? _weakReferenceToSelf;
~NodePath()
{
@@ -135,7 +138,8 @@ namespace Godot
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <param name="from">The <see cref="NodePath"/> to convert.</param>
- public static implicit operator string(NodePath from) => from?.ToString();
+ [return: NotNullIfNotNull("from")]
+ public static implicit operator string?(NodePath? from) => from?.ToString();
/// <summary>
/// Converts this <see cref="NodePath"/> to a string.
@@ -289,19 +293,19 @@ namespace Godot
/// <returns>If the <see cref="NodePath"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
- public static bool operator ==(NodePath left, NodePath right)
+ public static bool operator ==(NodePath? left, NodePath? right)
{
if (left is null)
return right is null;
return left.Equals(right);
}
- public static bool operator !=(NodePath left, NodePath right)
+ public static bool operator !=(NodePath? left, NodePath? right)
{
return !(left == right);
}
- public bool Equals(NodePath other)
+ public bool Equals([NotNullWhen(true)] NodePath? other)
{
if (other is null)
return false;
@@ -310,7 +314,7 @@ namespace Godot
return NativeFuncs.godotsharp_node_path_equals(self, otherNative).ToBool();
}
- public override bool Equals(object obj)
+ public override bool Equals([NotNullWhen(true)] object? obj)
{
return ReferenceEquals(this, obj) || (obj is NodePath other && Equals(other));
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
index 85b2b02c45..f5dc34d824 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -382,7 +385,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the plane and the other object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Plane other && Equals(other);
}
@@ -430,7 +433,7 @@ namespace Godot
/// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this plane.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"{_normal.ToString(format)}, {_d.ToString(format)}";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
index 155e90ff24..4c9e21fb79 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -586,7 +589,7 @@ namespace Godot
public readonly Vector2 GetFarPlaneHalfExtents()
{
var res = GetProjectionPlane(Planes.Far).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top));
- return new Vector2(res.Value.X, res.Value.Y);
+ return res is null ? default : new Vector2(res.Value.X, res.Value.Y);
}
/// <summary>
@@ -597,7 +600,7 @@ namespace Godot
public readonly Vector2 GetViewportHalfExtents()
{
var res = GetProjectionPlane(Planes.Near).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top));
- return new Vector2(res.Value.X, res.Value.Y);
+ return res is null ? default : new Vector2(res.Value.X, res.Value.Y);
}
/// <summary>
@@ -981,7 +984,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Projection other && Equals(other);
}
@@ -1018,7 +1021,7 @@ namespace Godot
/// Converts this <see cref="Projection"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this projection.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"{X.X.ToString(format)}, {X.Y.ToString(format)}, {X.Z.ToString(format)}, {X.W.ToString(format)}\n" +
$"{Y.X.ToString(format)}, {Y.Y.ToString(format)}, {Y.Z.ToString(format)}, {Y.W.ToString(format)}\n" +
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
index 3d45913586..2344e8c510 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -769,7 +772,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the quaternion and the other object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Quaternion other && Equals(other);
}
@@ -817,7 +820,7 @@ namespace Godot
/// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this quaternion.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)}, {W.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index babb26960b..cf4ac45a9f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -120,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>
@@ -427,7 +430,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the rect and the other object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rect2 other && Equals(other);
}
@@ -475,7 +478,7 @@ namespace Godot
/// Converts this <see cref="Rect2"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this rect.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index 49fba02b54..58560df0c5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -110,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>
@@ -398,7 +401,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the rect and the other object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rect2I other && Equals(other);
}
@@ -435,7 +438,7 @@ namespace Godot
/// Converts this <see cref="Rect2I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this rect.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
index 350626389b..fccae94eac 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
@@ -1,8 +1,11 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -71,7 +74,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the color and the other object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rid other && Equals(other);
}
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 97d28f9ee9..21d9ada127 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -10,11 +13,11 @@ namespace Godot
/// Comparing them is much faster than with regular strings, because only the pointers are compared,
/// not the whole strings.
/// </summary>
- public sealed class StringName : IDisposable, IEquatable<StringName>
+ public sealed class StringName : IDisposable, IEquatable<StringName?>
{
internal godot_string_name.movable NativeValue;
- private WeakReference<IDisposable> _weakReferenceToSelf;
+ private WeakReference<IDisposable>? _weakReferenceToSelf;
~StringName()
{
@@ -81,7 +84,8 @@ namespace Godot
/// Converts a <see cref="StringName"/> to a string.
/// </summary>
/// <param name="from">The <see cref="StringName"/> to convert.</param>
- public static implicit operator string(StringName from) => from?.ToString();
+ [return: NotNullIfNotNull("from")]
+ public static implicit operator string?(StringName? from) => from?.ToString();
/// <summary>
/// Converts this <see cref="StringName"/> to a string.
@@ -104,43 +108,43 @@ namespace Godot
/// <returns>If the <see cref="StringName"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
- public static bool operator ==(StringName left, StringName right)
+ public static bool operator ==(StringName? left, StringName? right)
{
if (left is null)
return right is null;
return left.Equals(right);
}
- public static bool operator !=(StringName left, StringName right)
+ public static bool operator !=(StringName? left, StringName? right)
{
return !(left == right);
}
- public bool Equals(StringName other)
+ public bool Equals([NotNullWhen(true)] StringName? other)
{
if (other is null)
return false;
return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef;
}
- public static bool operator ==(StringName left, in godot_string_name right)
+ public static bool operator ==(StringName? left, in godot_string_name right)
{
if (left is null)
return right.IsEmpty;
return left.Equals(right);
}
- public static bool operator !=(StringName left, in godot_string_name right)
+ public static bool operator !=(StringName? left, in godot_string_name right)
{
return !(left == right);
}
- public static bool operator ==(in godot_string_name left, StringName right)
+ public static bool operator ==(in godot_string_name left, StringName? right)
{
return right == left;
}
- public static bool operator !=(in godot_string_name left, StringName right)
+ public static bool operator !=(in godot_string_name left, StringName? right)
{
return !(right == left);
}
@@ -150,7 +154,7 @@ namespace Godot
return NativeValue.DangerousSelfRef == other;
}
- public override bool Equals(object obj)
+ public override bool Equals([NotNullWhen(true)] object? obj)
{
return ReferenceEquals(this, obj) || (obj is StringName other && Equals(other));
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 386f587464..3443277fee 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -1,7 +1,10 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -606,7 +609,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the transform and the object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Transform2D other && Equals(other);
}
@@ -656,7 +659,7 @@ namespace Godot
/// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this transform.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"[X: {X.ToString(format)}, Y: {Y.ToString(format)}, O: {Origin.ToString(format)}]";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
index 2d09259dcb..f80c0bd8dd 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
@@ -1,7 +1,10 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.ComponentModel;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -630,7 +633,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the transform and the object are exactly equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Transform3D other && Equals(other);
}
@@ -680,7 +683,7 @@ namespace Godot
/// Converts this <see cref="Transform3D"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this transform.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"[X: {Basis.X.ToString(format)}, Y: {Basis.Y.ToString(format)}, Z: {Basis.Z.ToString(format)}, O: {Origin.ToString(format)}]";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 4842dbc9af..a27a1ab1cf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -954,7 +957,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector2 other && Equals(other);
}
@@ -1016,7 +1019,7 @@ namespace Godot
/// Converts this <see cref="Vector2"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
index 4ee452455e..8960323754 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -121,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"/>
@@ -182,8 +208,8 @@ namespace Godot
}
// Constants
- private static readonly Vector2I _min = new Vector2I(int.MinValue, int.MinValue);
- private static readonly Vector2I _max = new Vector2I(int.MaxValue, int.MaxValue);
+ private static readonly Vector2I _minValue = new Vector2I(int.MinValue, int.MinValue);
+ private static readonly Vector2I _maxValue = new Vector2I(int.MaxValue, int.MaxValue);
private static readonly Vector2I _zero = new Vector2I(0, 0);
private static readonly Vector2I _one = new Vector2I(1, 1);
@@ -197,12 +223,12 @@ namespace Godot
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector2.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(int.MinValue, int.MinValue)</c>.</value>
- public static Vector2I Min { get { return _min; } }
+ public static Vector2I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector2.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(int.MaxValue, int.MaxValue)</c>.</value>
- public static Vector2I Max { get { return _max; } }
+ public static Vector2I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
@@ -535,7 +561,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector2I other && Equals(other);
}
@@ -572,7 +598,7 @@ namespace Godot
/// Converts this <see cref="Vector2I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index d26d4662a0..54d698345f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -1056,7 +1059,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector3 other && Equals(other);
}
@@ -1118,7 +1121,7 @@ namespace Godot
/// Converts this <see cref="Vector3"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
index db8ceb30e9..2d7bbc926d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -129,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"/>
@@ -193,8 +219,8 @@ namespace Godot
}
// Constants
- private static readonly Vector3I _min = new Vector3I(int.MinValue, int.MinValue, int.MinValue);
- private static readonly Vector3I _max = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue);
+ private static readonly Vector3I _minValue = new Vector3I(int.MinValue, int.MinValue, int.MinValue);
+ private static readonly Vector3I _maxValue = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue);
private static readonly Vector3I _zero = new Vector3I(0, 0, 0);
private static readonly Vector3I _one = new Vector3I(1, 1, 1);
@@ -210,12 +236,12 @@ namespace Godot
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector3.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(int.MinValue, int.MinValue, int.MinValue)</c>.</value>
- public static Vector3I Min { get { return _min; } }
+ public static Vector3I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector3.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value>
- public static Vector3I Max { get { return _max; } }
+ public static Vector3I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
@@ -590,7 +616,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector3I other && Equals(other);
}
@@ -627,7 +653,7 @@ namespace Godot
/// Converts this <see cref="Vector3I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
index eeaef5e46e..87c01ad5ea 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -838,7 +841,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector4 other && Equals(other);
}
@@ -900,7 +903,7 @@ namespace Godot
/// Converts this <see cref="Vector4"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)}, {W.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
index e75e996b04..9a85f359d7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
@@ -1,6 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+#nullable enable
+
namespace Godot
{
/// <summary>
@@ -146,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"/>
@@ -228,8 +254,8 @@ namespace Godot
}
// Constants
- private static readonly Vector4I _min = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue);
- private static readonly Vector4I _max = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue);
+ private static readonly Vector4I _minValue = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue);
+ private static readonly Vector4I _maxValue = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue);
private static readonly Vector4I _zero = new Vector4I(0, 0, 0, 0);
private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1);
@@ -238,12 +264,12 @@ namespace Godot
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector4.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue)</c>.</value>
- public static Vector4I Min { get { return _min; } }
+ public static Vector4I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector4.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value>
- public static Vector4I Max { get { return _max; } }
+ public static Vector4I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
@@ -611,7 +637,7 @@ namespace Godot
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
- public override readonly bool Equals(object obj)
+ public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector4I other && Equals(other);
}
@@ -648,7 +674,7 @@ namespace Godot
/// Converts this <see cref="Vector4I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
- public readonly string ToString(string format)
+ public readonly string ToString(string? format)
{
return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)}), {W.ToString(format)})";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index a55b8d693b..db16b1fe1d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -17,7 +17,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>GodotSharp</PackageId>
- <Version>4.2.0</Version>
+ <Version>4.3.0</Version>
<PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index db9337d4eb..31e20e4ecd 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -15,7 +15,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>GodotSharpEditor</PackageId>
- <Version>4.2.0</Version>
+ <Version>4.3.0</Version>
<PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
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..7ce2232aa4 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -395,6 +395,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.h b/modules/mono/mono_gd/gd_mono.h
index 530936cfb4..1b46a619ca 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -68,7 +68,9 @@ class GDMono {
String project_assembly_path;
uint64_t project_assembly_modified_time = 0;
+#ifdef GD_MONO_HOT_RELOAD
int project_load_failure_count = 0;
+#endif
#ifdef TOOLS_ENABLED
bool _load_project_assembly();
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/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 04de3dfb7f..665b246bc5 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -483,9 +483,14 @@ Error SceneMultiplayer::complete_auth(int p_peer) {
ERR_FAIL_COND_V(!pending_peers.has(p_peer), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(pending_peers[p_peer].local, ERR_FILE_CANT_WRITE, "The authentication session was already marked as completed.");
pending_peers[p_peer].local = true;
+
// Notify the remote peer that the authentication has completed.
uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH };
+ multiplayer_peer->set_target_peer(p_peer);
+ multiplayer_peer->set_transfer_channel(0);
+ multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
Error err = _send(buf, 2);
+
// The remote peer already reported the authentication as completed, so admit the peer.
// May generate new packets, so it must happen after sending confirmation.
if (pending_peers[p_peer].remote) {
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index c95e4ff9c9..31307e648d 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -707,7 +707,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);
@@ -799,7 +799,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 6a3bf6793e..5a27f315b9 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -331,6 +331,13 @@ RID GodotNavigationServer::agent_get_map(RID p_agent) const {
return RID();
}
+Vector3 GodotNavigationServer::map_get_random_point(RID p_map, uint32_t p_navigation_layers, bool p_uniformly) const {
+ const NavMap *map = map_owner.get_or_null(p_map);
+ ERR_FAIL_NULL_V(map, Vector3());
+
+ return map->get_random_point(p_navigation_layers, p_uniformly);
+}
+
RID GodotNavigationServer::region_create() {
MutexLock lock(operations_mutex);
@@ -384,6 +391,13 @@ COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform) {
region->set_transform(p_transform);
}
+Transform3D GodotNavigationServer::region_get_transform(RID p_region) const {
+ NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Transform3D());
+
+ return region->get_transform();
+}
+
COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL(region);
@@ -498,6 +512,13 @@ Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, i
return region->get_connection_pathway_end(p_connection_id);
}
+Vector3 GodotNavigationServer::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_random_point(p_navigation_layers, p_uniformly);
+}
+
RID GodotNavigationServer::link_create() {
MutexLock lock(operations_mutex);
@@ -705,6 +726,13 @@ COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) {
agent->set_neighbor_distance(p_distance);
}
+real_t GodotNavigationServer::agent_get_neighbor_distance(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_neighbor_distance();
+}
+
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
@@ -712,22 +740,43 @@ COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
agent->set_max_neighbors(p_count);
}
+int GodotNavigationServer::agent_get_max_neighbors(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_max_neighbors();
+}
+
COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) {
- ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive.");
+ ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizon must be positive.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
agent->set_time_horizon_agents(p_time_horizon);
}
+real_t GodotNavigationServer::agent_get_time_horizon_agents(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_time_horizon_agents();
+}
+
COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon) {
- ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive.");
+ ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizon must be positive.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
agent->set_time_horizon_obstacles(p_time_horizon);
}
+real_t GodotNavigationServer::agent_get_time_horizon_obstacles(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_time_horizon_obstacles();
+}
+
COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
@@ -736,6 +785,13 @@ COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) {
agent->set_radius(p_radius);
}
+real_t GodotNavigationServer::agent_get_radius(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_radius();
+}
+
COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) {
ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
@@ -744,6 +800,13 @@ COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) {
agent->set_height(p_height);
}
+real_t GodotNavigationServer::agent_get_height(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_height();
+}
+
COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) {
ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
@@ -752,6 +815,13 @@ COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) {
agent->set_max_speed(p_max_speed);
}
+real_t GodotNavigationServer::agent_get_max_speed(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_max_speed();
+}
+
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
@@ -759,6 +829,13 @@ COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
agent->set_velocity(p_velocity);
}
+Vector3 GodotNavigationServer::agent_get_velocity(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, Vector3());
+
+ return agent->get_velocity();
+}
+
COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
@@ -773,6 +850,13 @@ COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) {
agent->set_position(p_position);
}
+Vector3 GodotNavigationServer::agent_get_position(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, Vector3());
+
+ return agent->get_position();
+}
+
bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL_V(agent, false);
@@ -795,18 +879,39 @@ COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) {
}
}
+bool GodotNavigationServer::agent_has_avoidance_callback(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, false);
+
+ return agent->has_avoidance_callback();
+}
+
COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
agent->set_avoidance_layers(p_layers);
}
+uint32_t GodotNavigationServer::agent_get_avoidance_layers(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_avoidance_layers();
+}
+
COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_NULL(agent);
agent->set_avoidance_mask(p_mask);
}
+uint32_t GodotNavigationServer::agent_get_avoidance_mask(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_avoidance_mask();
+}
+
COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) {
ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive.");
ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive.");
@@ -815,6 +920,13 @@ COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) {
agent->set_avoidance_priority(p_priority);
}
+real_t GodotNavigationServer::agent_get_avoidance_priority(RID p_agent) const {
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
+ ERR_FAIL_NULL_V(agent, 0);
+
+ return agent->get_avoidance_priority();
+}
+
RID GodotNavigationServer::obstacle_create() {
MutexLock lock(operations_mutex);
@@ -899,12 +1011,26 @@ COMMAND_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius) {
obstacle->set_radius(p_radius);
}
+real_t GodotNavigationServer::obstacle_get_radius(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, 0);
+
+ return obstacle->get_radius();
+}
+
COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
ERR_FAIL_NULL(obstacle);
obstacle->set_height(p_height);
}
+real_t GodotNavigationServer::obstacle_get_height(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, 0);
+
+ return obstacle->get_height();
+}
+
COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
ERR_FAIL_NULL(obstacle);
@@ -912,24 +1038,52 @@ COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity) {
obstacle->set_velocity(p_velocity);
}
+Vector3 GodotNavigationServer::obstacle_get_velocity(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, Vector3());
+
+ return obstacle->get_velocity();
+}
+
COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
ERR_FAIL_NULL(obstacle);
obstacle->set_position(p_position);
}
+Vector3 GodotNavigationServer::obstacle_get_position(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, Vector3());
+
+ return obstacle->get_position();
+}
+
void GodotNavigationServer::obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
ERR_FAIL_NULL(obstacle);
obstacle->set_vertices(p_vertices);
}
+Vector<Vector3> GodotNavigationServer::obstacle_get_vertices(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, Vector<Vector3>());
+
+ return obstacle->get_vertices();
+}
+
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
ERR_FAIL_NULL(obstacle);
obstacle->set_avoidance_layers(p_layers);
}
+uint32_t GodotNavigationServer::obstacle_get_avoidance_layers(RID p_obstacle) const {
+ NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
+ ERR_FAIL_NULL_V(obstacle, 0);
+
+ return obstacle->get_avoidance_layers();
+}
+
void GodotNavigationServer::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) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 4ead4fc398..f19450db27 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -140,6 +140,8 @@ public:
virtual void map_force_update(RID p_map) override;
+ virtual Vector3 map_get_random_point(RID p_map, uint32_t p_navigation_layers, bool p_uniformly) const override;
+
virtual RID region_create() override;
COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled);
@@ -163,6 +165,7 @@ public:
COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers);
virtual uint32_t region_get_navigation_layers(RID p_region) const override;
COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform);
+ virtual Transform3D region_get_transform(RID p_region) const override;
COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh);
#ifndef DISABLE_DEPRECATED
virtual void region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) override;
@@ -170,6 +173,7 @@ public:
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
COMMAND_2(link_set_map, RID, p_link, RID, p_map);
@@ -201,20 +205,33 @@ public:
COMMAND_2(agent_set_paused, RID, p_agent, bool, p_paused);
virtual bool agent_get_paused(RID p_agent) const override;
COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance);
+ virtual real_t agent_get_neighbor_distance(RID p_agent) const override;
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count);
+ virtual int agent_get_max_neighbors(RID p_agent) const override;
COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon);
+ virtual real_t agent_get_time_horizon_agents(RID p_agent) const override;
COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon);
+ virtual real_t agent_get_time_horizon_obstacles(RID p_agent) const override;
COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius);
+ virtual real_t agent_get_radius(RID p_agent) const override;
COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height);
+ virtual real_t agent_get_height(RID p_agent) const override;
COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed);
+ virtual real_t agent_get_max_speed(RID p_agent) const override;
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity);
+ virtual Vector3 agent_get_velocity(RID p_agent) const override;
COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity);
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position);
+ virtual Vector3 agent_get_position(RID p_agent) const override;
virtual bool agent_is_map_changed(RID p_agent) const override;
COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback);
+ virtual bool agent_has_avoidance_callback(RID p_agent) const override;
COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers);
+ virtual uint32_t agent_get_avoidance_layers(RID p_agent) const override;
COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask);
+ virtual uint32_t agent_get_avoidance_mask(RID p_agent) const override;
COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority);
+ virtual real_t agent_get_avoidance_priority(RID p_agent) const override;
virtual RID obstacle_create() override;
COMMAND_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled);
@@ -226,11 +243,17 @@ public:
COMMAND_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused);
virtual bool obstacle_get_paused(RID p_obstacle) const override;
COMMAND_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius);
+ virtual real_t obstacle_get_radius(RID p_obstacle) const override;
COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity);
+ virtual Vector3 obstacle_get_velocity(RID p_obstacle) const override;
COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position);
+ virtual Vector3 obstacle_get_position(RID p_obstacle) const override;
COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height);
+ virtual real_t obstacle_get_height(RID p_obstacle) const override;
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override;
+ virtual Vector<Vector3> obstacle_get_vertices(RID p_obstacle) const override;
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers);
+ virtual uint32_t obstacle_get_avoidance_layers(RID p_obstacle) const override;
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;
diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp
index b54729e06f..76bfd3a101 100644
--- a/modules/navigation/godot_navigation_server_2d.cpp
+++ b/modules/navigation/godot_navigation_server_2d.cpp
@@ -141,6 +141,13 @@ static Transform3D trf2_to_trf3(const Transform2D &d) {
return Transform3D(b, o);
}
+static Transform2D trf3_to_trf2(const Transform3D &d) {
+ Vector3 o = d.get_origin();
+ Vector3 nx = d.xform(Vector3(1, 0, 0)) - o;
+ Vector3 nz = d.xform(Vector3(0, 0, 1)) - o;
+ return Transform2D(nx.x, nx.z, nz.x, nz.z, o.x, o.z);
+}
+
static ObjectID id_to_id(const ObjectID &id) {
return id;
}
@@ -259,8 +266,12 @@ Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2
Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
-RID FORWARD_0(region_create);
+Vector2 GodotNavigationServer2D::map_get_random_point(RID p_map, uint32_t p_naviation_layers, bool p_uniformly) const {
+ Vector3 result = NavigationServer3D::get_singleton()->map_get_random_point(p_map, p_naviation_layers, p_uniformly);
+ return v3_to_v2(result);
+}
+RID FORWARD_0(region_create);
void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid);
void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
@@ -279,6 +290,10 @@ void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigati
uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid);
void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3);
+Transform2D GodotNavigationServer2D::region_get_transform(RID p_region) const {
+ return trf3_to_trf2(NavigationServer3D::get_singleton()->region_get_transform(p_region));
+}
+
void GodotNavigationServer2D::region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) {
NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon));
}
@@ -287,6 +302,11 @@ int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
+Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
+ Vector3 result = NavigationServer3D::get_singleton()->region_get_random_point(p_region, p_navigation_layers, p_uniformly);
+ return v3_to_v2(result);
+}
+
RID FORWARD_0(link_create);
void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid);
@@ -317,25 +337,60 @@ void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_t
bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid);
void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid);
void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_neighbor_distance(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_neighbor_distance(p_agent);
+}
void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int);
+int GodotNavigationServer2D::agent_get_max_neighbors(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_max_neighbors(p_agent);
+}
void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_time_horizon_agents(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_time_horizon_agents(p_agent);
+}
void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_time_horizon_obstacles(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_time_horizon_obstacles(p_agent);
+}
void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_radius(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_radius(p_agent);
+}
void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_max_speed(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_max_speed(p_agent);
+}
void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+Vector2 GodotNavigationServer2D::agent_get_velocity(RID p_agent) const {
+ return v3_to_v2(NavigationServer3D::get_singleton()->agent_get_velocity(p_agent));
+}
void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3);
-
+Vector2 GodotNavigationServer2D::agent_get_position(RID p_agent) const {
+ return v3_to_v2(NavigationServer3D::get_singleton()->agent_get_position(p_agent));
+}
bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid);
void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool);
bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid);
void FORWARD_1(free, RID, p_object, rid_to_rid);
void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable);
+bool GodotNavigationServer2D::agent_has_avoidance_callback(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_has_avoidance_callback(p_agent);
+}
void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+uint32_t GodotNavigationServer2D::agent_get_avoidance_layers(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_avoidance_layers(p_agent);
+}
void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32);
+uint32_t GodotNavigationServer2D::agent_get_avoidance_mask(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_avoidance_mask(p_agent);
+}
void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::agent_get_avoidance_priority(RID p_agent) const {
+ return NavigationServer3D::get_singleton()->agent_get_avoidance_priority(p_agent);
+}
RID GodotNavigationServer2D::obstacle_create() {
RID obstacle = NavigationServer3D::get_singleton()->obstacle_create();
@@ -348,13 +403,28 @@ RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid);
void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool);
bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid);
void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real);
+real_t GodotNavigationServer2D::obstacle_get_radius(RID p_obstacle) const {
+ return NavigationServer3D::get_singleton()->obstacle_get_radius(p_obstacle);
+}
void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+Vector2 GodotNavigationServer2D::obstacle_get_velocity(RID p_obstacle) const {
+ return v3_to_v2(NavigationServer3D::get_singleton()->obstacle_get_velocity(p_obstacle));
+}
void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3);
+Vector2 GodotNavigationServer2D::obstacle_get_position(RID p_obstacle) const {
+ return v3_to_v2(NavigationServer3D::get_singleton()->obstacle_get_position(p_obstacle));
+}
void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+uint32_t GodotNavigationServer2D::obstacle_get_avoidance_layers(RID p_obstacle) const {
+ return NavigationServer3D::get_singleton()->obstacle_get_avoidance_layers(p_obstacle);
+}
void GodotNavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) {
NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices));
}
+Vector<Vector2> GodotNavigationServer2D::obstacle_get_vertices(RID p_obstacle) const {
+ return vector_v3_to_v2(NavigationServer3D::get_singleton()->obstacle_get_vertices(p_obstacle));
+}
void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const {
ERR_FAIL_COND(!p_query_parameters.is_valid());
diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h
index 337f5f40d8..2f473da1ab 100644
--- a/modules/navigation/godot_navigation_server_2d.h
+++ b/modules/navigation/godot_navigation_server_2d.h
@@ -76,6 +76,7 @@ public:
virtual TypedArray<RID> map_get_agents(RID p_map) const override;
virtual TypedArray<RID> map_get_obstacles(RID p_map) const override;
virtual void map_force_update(RID p_map) override;
+ virtual Vector2 map_get_random_point(RID p_map, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID region_create() override;
virtual void region_set_enabled(RID p_region, bool p_enabled) override;
@@ -94,10 +95,12 @@ public:
virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override;
virtual uint32_t region_get_navigation_layers(RID p_region) const override;
virtual void region_set_transform(RID p_region, Transform2D p_transform) override;
+ virtual Transform2D region_get_transform(RID p_region) const override;
virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) override;
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
@@ -157,6 +160,7 @@ public:
/// low, the simulation will not be safe.
/// Must be non-negative.
virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override;
+ virtual real_t agent_get_neighbor_distance(RID p_agent) const override;
/// The maximum number of other agents this
/// agent takes into account in the navigation.
@@ -165,6 +169,7 @@ public:
/// number is too low, the simulation will not
/// be safe.
virtual void agent_set_max_neighbors(RID p_agent, int p_count) override;
+ virtual int agent_get_max_neighbors(RID p_agent) const override;
/// The minimal amount of time for which this
/// agent's velocities that are computed by the
@@ -174,17 +179,20 @@ public:
/// other agents, but the less freedom this
/// agent has in choosing its velocities.
/// Must be positive.
-
virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override;
+ virtual real_t agent_get_time_horizon_agents(RID p_agent) const override;
virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override;
+ virtual real_t agent_get_time_horizon_obstacles(RID p_agent) const override;
/// The radius of this agent.
/// Must be non-negative.
virtual void agent_set_radius(RID p_agent, real_t p_radius) override;
+ virtual real_t agent_get_radius(RID p_agent) const override;
/// The maximum speed of this agent.
/// Must be non-negative.
virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) override;
+ virtual real_t agent_get_max_speed(RID p_agent) const override;
/// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly
virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override;
@@ -192,19 +200,27 @@ public:
/// The wanted velocity for the agent as a "suggestion" to the avoidance simulation.
/// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'.
virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) override;
+ virtual Vector2 agent_get_velocity(RID p_agent) const override;
/// Position of the agent in world space.
virtual void agent_set_position(RID p_agent, Vector2 p_position) override;
+ virtual Vector2 agent_get_position(RID p_agent) const override;
/// Returns true if the map got changed the previous frame.
virtual bool agent_is_map_changed(RID p_agent) const override;
/// Callback called at the end of the RVO process
virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override;
+ virtual bool agent_has_avoidance_callback(RID p_agent) const override;
virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override;
+ virtual uint32_t agent_get_avoidance_layers(RID p_agent) const override;
+
virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override;
+ virtual uint32_t agent_get_avoidance_mask(RID p_agent) const override;
+
virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override;
+ virtual real_t agent_get_avoidance_priority(RID p_agent) const override;
virtual RID obstacle_create() override;
virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override;
@@ -214,10 +230,15 @@ public:
virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) override;
virtual bool obstacle_get_paused(RID p_obstacle) const override;
virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) override;
+ virtual real_t obstacle_get_radius(RID p_obstacle) const override;
virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override;
+ virtual Vector2 obstacle_get_velocity(RID p_obstacle) const override;
virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override;
+ virtual Vector2 obstacle_get_position(RID p_obstacle) const override;
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override;
+ virtual Vector<Vector2> obstacle_get_vertices(RID p_obstacle) const override;
virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override;
+ virtual uint32_t obstacle_get_avoidance_layers(RID p_obstacle) const override;
virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override;
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 5ef0298eb4..6429513b53 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -372,7 +372,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
// Stores the further reachable end polygon, in case our goal is not reachable.
if (is_reachable) {
- real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination) * navigation_polys[least_cost_id].poly->owner->get_travel_cost();
+ real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination);
if (reachable_d > d) {
reachable_d = d;
reachable_end = navigation_polys[least_cost_id].poly;
@@ -769,6 +769,70 @@ void NavMap::remove_agent_as_controlled(NavAgent *agent) {
}
}
+Vector3 NavMap::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ const LocalVector<NavRegion *> map_regions = get_regions();
+
+ if (map_regions.is_empty()) {
+ return Vector3();
+ }
+
+ LocalVector<const NavRegion *> accessible_regions;
+
+ for (const NavRegion *region : map_regions) {
+ if (!region->get_enabled() || (p_navigation_layers & region->get_navigation_layers()) == 0) {
+ continue;
+ }
+ accessible_regions.push_back(region);
+ }
+
+ if (accessible_regions.is_empty()) {
+ // All existing region polygons are disabled.
+ return Vector3();
+ }
+
+ if (p_uniformly) {
+ real_t accumulated_region_surface_area = 0;
+ RBMap<real_t, uint32_t> accessible_regions_area_map;
+
+ for (uint32_t accessible_region_index = 0; accessible_region_index < accessible_regions.size(); accessible_region_index++) {
+ const NavRegion *region = accessible_regions[accessible_region_index];
+
+ real_t region_surface_area = region->get_surface_area();
+
+ if (region_surface_area == 0.0f) {
+ continue;
+ }
+
+ accessible_regions_area_map[accumulated_region_surface_area] = accessible_region_index;
+ accumulated_region_surface_area += region_surface_area;
+ }
+ if (accessible_regions_area_map.is_empty() || accumulated_region_surface_area == 0) {
+ // All faces have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t random_accessible_regions_area_map = Math::random(real_t(0), accumulated_region_surface_area);
+
+ RBMap<real_t, uint32_t>::Iterator E = accessible_regions_area_map.find_closest(random_accessible_regions_area_map);
+ ERR_FAIL_COND_V(!E, Vector3());
+ uint32_t random_region_index = E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(random_region_index, accessible_regions.size(), Vector3());
+
+ const NavRegion *random_region = accessible_regions[random_region_index];
+ ERR_FAIL_NULL_V(random_region, Vector3());
+
+ return random_region->get_random_point(p_navigation_layers, p_uniformly);
+
+ } else {
+ uint32_t random_region_index = Math::random(int(0), accessible_regions.size() - 1);
+
+ const NavRegion *random_region = accessible_regions[random_region_index];
+ ERR_FAIL_NULL_V(random_region, Vector3());
+
+ return random_region->get_random_point(p_navigation_layers, p_uniformly);
+ }
+}
+
void NavMap::sync() {
// Performance Monitor
int _new_pm_region_count = regions.size();
@@ -1107,8 +1171,14 @@ void NavMap::_update_rvo_obstacles_tree_2d() {
obstacle_vertex_count += obstacle->get_vertices().size();
}
+ // Cleaning old obstacles.
+ for (size_t i = 0; i < rvo_simulation_2d.obstacles_.size(); ++i) {
+ delete rvo_simulation_2d.obstacles_[i];
+ }
+ rvo_simulation_2d.obstacles_.clear();
+
// Cannot use LocalVector here as RVO library expects std::vector to build KdTree
- std::vector<RVO2D::Obstacle2D *> raw_obstacles;
+ std::vector<RVO2D::Obstacle2D *> &raw_obstacles = rvo_simulation_2d.obstacles_;
raw_obstacles.reserve(obstacle_vertex_count);
// The following block is modified copy from RVO2D::AddObstacle()
@@ -1125,8 +1195,14 @@ void NavMap::_update_rvo_obstacles_tree_2d() {
rvo_2d_vertices.reserve(_obstacle_vertices.size());
uint32_t _obstacle_avoidance_layers = obstacle->get_avoidance_layers();
+ real_t _obstacle_height = obstacle->get_height();
for (const Vector3 &_obstacle_vertex : _obstacle_vertices) {
+#ifdef TOOLS_ENABLED
+ if (_obstacle_vertex.y != 0) {
+ WARN_PRINT_ONCE("Y coordinates of static obstacle vertices are ignored. Please use obstacle position Y to change elevation of obstacle.");
+ }
+#endif
rvo_2d_vertices.push_back(RVO2D::Vector2(_obstacle_vertex.x + _obstacle_position.x, _obstacle_vertex.z + _obstacle_position.z));
}
@@ -1135,6 +1211,9 @@ void NavMap::_update_rvo_obstacles_tree_2d() {
for (size_t i = 0; i < rvo_2d_vertices.size(); i++) {
RVO2D::Obstacle2D *rvo_2d_obstacle = new RVO2D::Obstacle2D();
rvo_2d_obstacle->point_ = rvo_2d_vertices[i];
+ rvo_2d_obstacle->height_ = _obstacle_height;
+ rvo_2d_obstacle->elevation_ = _obstacle_position.y;
+
rvo_2d_obstacle->avoidance_layers_ = _obstacle_avoidance_layers;
if (i != 0) {
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index 5d78c14627..e8cbe7e247 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -190,6 +190,8 @@ public:
return map_update_id;
}
+ Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const;
+
void sync();
void step(real_t p_deltatime);
void dispatch_callbacks();
diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp
index f8c12935b4..6dfafa4e91 100644
--- a/modules/navigation/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/nav_mesh_generator_2d.cpp
@@ -591,13 +591,19 @@ void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygo
continue;
}
+ // Transform flags.
+ const int alternative_id = tilemap->get_cell_alternative_tile(tilemap_layer, cell, false);
+ bool flip_h = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H);
+ bool flip_v = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V);
+ bool transpose = (alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+
Transform2D tile_transform;
tile_transform.set_origin(tilemap->map_to_local(cell));
const Transform2D tile_transform_offset = tilemap_xform * tile_transform;
if (navigation_layers_count > 0) {
- Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer);
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer, flip_h, flip_v, transpose);
if (navigation_polygon.is_valid()) {
for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) {
const Vector<Vector2> &navigation_polygon_outline = navigation_polygon->get_outline(outline_index);
@@ -622,11 +628,15 @@ void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygo
if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) {
for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) {
- const Vector<Vector2> &collision_polygon_points = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index);
+ PackedVector2Array collision_polygon_points = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index);
if (collision_polygon_points.size() == 0) {
continue;
}
+ if (flip_h || flip_v || transpose) {
+ collision_polygon_points = TileData::get_transformed_vertices(collision_polygon_points, flip_h, flip_v, transpose);
+ }
+
Vector<Vector2> obstruction_outline;
obstruction_outline.resize(collision_polygon_points.size());
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 09697c7be0..9cb235d79f 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -100,6 +100,88 @@ Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const {
return connections[p_connection_id].pathway_end;
}
+Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ if (!get_enabled()) {
+ return Vector3();
+ }
+
+ const LocalVector<gd::Polygon> &region_polygons = get_polygons();
+
+ if (region_polygons.is_empty()) {
+ return Vector3();
+ }
+
+ if (p_uniformly) {
+ real_t accumulated_area = 0;
+ RBMap<real_t, uint32_t> region_area_map;
+
+ for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) {
+ const gd::Polygon &region_polygon = region_polygons[rp_index];
+ real_t polyon_area = region_polygon.surface_area;
+
+ if (polyon_area == 0.0) {
+ continue;
+ }
+ region_area_map[accumulated_area] = rp_index;
+ accumulated_area += polyon_area;
+ }
+ if (region_area_map.is_empty() || accumulated_area == 0) {
+ // All polygons have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t region_area_map_pos = Math::random(real_t(0), accumulated_area);
+
+ RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos);
+ ERR_FAIL_COND_V(!region_E, Vector3());
+ uint32_t rrp_polygon_index = region_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3());
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ real_t accumulated_polygon_area = 0;
+ RBMap<real_t, uint32_t> polygon_area_map;
+
+ for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) {
+ real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area();
+
+ if (face_area == 0.0) {
+ continue;
+ }
+ polygon_area_map[accumulated_polygon_area] = rpp_index;
+ accumulated_polygon_area += face_area;
+ }
+ if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) {
+ // All faces have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area);
+
+ RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos);
+ ERR_FAIL_COND_V(!polygon_E, Vector3());
+ uint32_t rrp_face_index = polygon_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3());
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+
+ } else {
+ uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1);
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1);
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+ }
+}
+
bool NavRegion::sync() {
bool something_changed = polygons_dirty /* || something_dirty? */;
@@ -113,6 +195,7 @@ void NavRegion::update_polygons() {
return;
}
polygons.clear();
+ surface_area = 0.0;
polygons_dirty = false;
if (map == nullptr) {
@@ -147,21 +230,46 @@ void NavRegion::update_polygons() {
polygons.resize(mesh->get_polygon_count());
+ real_t _new_region_surface_area = 0.0;
+
// Build
- for (size_t i(0); i < polygons.size(); i++) {
- gd::Polygon &p = polygons[i];
- p.owner = this;
+ int navigation_mesh_polygon_index = 0;
+ for (gd::Polygon &polygon : polygons) {
+ polygon.owner = this;
+ polygon.surface_area = 0.0;
- Vector<int> mesh_poly = mesh->get_polygon(i);
- const int *indices = mesh_poly.ptr();
+ Vector<int> navigation_mesh_polygon = mesh->get_polygon(navigation_mesh_polygon_index);
+ navigation_mesh_polygon_index += 1;
+
+ int navigation_mesh_polygon_size = navigation_mesh_polygon.size();
+ if (navigation_mesh_polygon_size < 3) {
+ continue;
+ }
+
+ const int *indices = navigation_mesh_polygon.ptr();
bool valid(true);
- p.points.resize(mesh_poly.size());
- p.edges.resize(mesh_poly.size());
- Vector3 center;
+ polygon.points.resize(navigation_mesh_polygon_size);
+ polygon.edges.resize(navigation_mesh_polygon_size);
+
+ real_t _new_polygon_surface_area = 0.0;
+
+ for (int j(2); j < navigation_mesh_polygon_size; j++) {
+ const Face3 face = Face3(
+ transform.xform(vertices_r[indices[0]]),
+ transform.xform(vertices_r[indices[j - 1]]),
+ transform.xform(vertices_r[indices[j]]));
+
+ _new_polygon_surface_area += face.get_area();
+ }
+
+ polygon.surface_area = _new_polygon_surface_area;
+ _new_region_surface_area += _new_polygon_surface_area;
+
+ Vector3 polygon_center;
real_t sum(0);
- for (int j(0); j < mesh_poly.size(); j++) {
+ for (int j(0); j < navigation_mesh_polygon_size; j++) {
int idx = indices[j];
if (idx < 0 || idx >= len) {
valid = false;
@@ -169,10 +277,10 @@ void NavRegion::update_polygons() {
}
Vector3 point_position = transform.xform(vertices_r[idx]);
- p.points[j].pos = point_position;
- p.points[j].key = map->get_point_key(point_position);
+ polygon.points[j].pos = point_position;
+ polygon.points[j].key = map->get_point_key(point_position);
- center += point_position; // Composing the center of the polygon
+ polygon_center += point_position; // Composing the center of the polygon
if (j >= 2) {
Vector3 epa = transform.xform(vertices_r[indices[j - 2]]);
@@ -186,9 +294,11 @@ void NavRegion::update_polygons() {
ERR_BREAK_MSG(!valid, "The navigation mesh set in this region is not valid!");
}
- p.clockwise = sum > 0;
- if (mesh_poly.size() != 0) {
- p.center = center / real_t(mesh_poly.size());
+ polygon.clockwise = sum > 0;
+ if (!navigation_mesh_polygon.is_empty()) {
+ polygon.center = polygon_center / real_t(navigation_mesh_polygon.size());
}
}
+
+ surface_area = _new_region_surface_area;
}
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index 6a8ebe5336..a9cfc53c7e 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -50,6 +50,8 @@ class NavRegion : public NavBase {
/// Cache
LocalVector<gd::Polygon> polygons;
+ real_t surface_area = 0.0;
+
public:
NavRegion() {
type = NavigationUtilities::PathSegmentType::PATH_SEGMENT_TYPE_REGION;
@@ -93,6 +95,10 @@ public:
return polygons;
}
+ Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const;
+
+ real_t get_surface_area() const { return surface_area; };
+
bool sync();
private:
diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h
index 6ddd8b9078..175d08ca6d 100644
--- a/modules/navigation/nav_utils.h
+++ b/modules/navigation/nav_utils.h
@@ -112,6 +112,8 @@ struct Polygon {
/// The center of this `Polygon`
Vector3 center;
+
+ real_t surface_area = 0.0;
};
struct NavigationPoly {
@@ -136,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/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp
index 1100367f03..1e6a9bbb6a 100644
--- a/modules/ogg/ogg_packet_sequence.cpp
+++ b/modules/ogg/ogg_packet_sequence.cpp
@@ -159,9 +159,7 @@ bool OggPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const {
*p_packet = packet;
- if (!packet->e_o_s) { // Added this so it doesn't try to go to the next packet if it's the last packet of the file.
- packet_cursor++;
- }
+ packet_cursor++;
return true;
}
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index c64b6de78c..285c58a96e 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -93,35 +93,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_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 72866f1cf7..bbcb63a7e6 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -177,7 +177,6 @@ void OpenXRActionMap::create_default_action_sets() {
Ref<OpenXRAction> trigger_touch = action_set->add_new_action("trigger_touch", "Trigger touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
- Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> grip_force = action_set->add_new_action("grip_force", "Grip force", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
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/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 6d1c215ffc..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" />
@@ -129,9 +136,11 @@
</member>
<member name="foveation_dynamic" type="bool" setter="set_foveation_dynamic" getter="get_foveation_dynamic" default="false">
Enable dynamic foveation adjustment, the interface must be initialized before this is accessible. If enabled foveation will automatically adjusted between low and [member foveation_level].
+ [b]Note:[/b] Only works on compatibility renderer.
</member>
<member name="foveation_level" type="int" setter="set_foveation_level" getter="get_foveation_level" default="0">
Set foveation level from 0 (off) to 3 (high), the interface must be initialized before this is accessible.
+ [b]Note:[/b] Only works on compatibility renderer.
</member>
<member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0">
The render size multiplier for the current HMD. Must be set before the interface has been initialized.
@@ -175,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_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..04fc4bf890 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,14 @@ 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;
+ }
+
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 +192,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 +256,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..a9d970bbb9 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"
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..a2f2577959 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"
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
index f31621fda0..2d0973bb6b 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
@@ -31,14 +31,14 @@
#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"
-// 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:
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index dc3ccccd08..eafabe03e7 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -46,11 +46,11 @@
#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"
@@ -87,7 +87,7 @@ String OpenXRAPI::get_default_action_map_resource_name() {
return name;
}
-String OpenXRAPI::get_error_string(XrResult result) {
+String OpenXRAPI::get_error_string(XrResult result) const {
if (XR_SUCCEEDED(result)) {
return String("Succeeded");
}
@@ -289,7 +289,7 @@ bool OpenXRAPI::create_instance() {
for (KeyValue<String, bool *> &requested_extension : requested_extensions) {
if (!is_extension_supported(requested_extension.key)) {
if (requested_extension.value == nullptr) {
- // Null means this is a manditory extension so we fail.
+ // Null means this is a mandatory extension so we fail.
ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!"));
} else {
// Set this extension as not supported.
@@ -665,13 +665,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;
}
@@ -699,16 +692,31 @@ bool OpenXRAPI::setup_spaces() {
// 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;
+ emulating_local_floor = false;
+
+ if (is_reference_space_supported(requested_reference_space)) {
+ 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.");
+
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ emulating_local_floor = true;
+
+ // 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;
+
+ } 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."));
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
}
XrReferenceSpaceCreateInfo play_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
reference_space, // referenceSpaceType
- identityPose // poseInReferenceSpace
+ identityPose, // poseInReferenceSpace
};
result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
@@ -742,6 +750,80 @@ bool OpenXRAPI::setup_spaces() {
return true;
}
+bool OpenXRAPI::reset_emulated_floor_height() {
+ ERR_FAIL_COND_V(!emulating_local_floor, 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;
+}
+
bool OpenXRAPI::load_supported_swapchain_formats() {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
@@ -1180,10 +1262,10 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
view_configuration = p_view_configuration;
}
-void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) {
+void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
ERR_FAIL_COND(is_initialized());
- reference_space = p_reference_space;
+ requested_reference_space = p_requested_reference_space;
}
void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
@@ -1261,6 +1343,7 @@ bool OpenXRAPI::resolve_instance_openxr_symbols() {
OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateFloat);
OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateVector2f);
OPENXR_API_INIT_XR_FUNC_V(xrGetCurrentInteractionProfile);
+ OPENXR_API_INIT_XR_FUNC_V(xrGetReferenceSpaceBoundsRect);
OPENXR_API_INIT_XR_FUNC_V(xrGetSystem);
OPENXR_API_INIT_XR_FUNC_V(xrGetSystemProperties);
OPENXR_API_INIT_XR_FUNC_V(xrLocateViews);
@@ -1627,6 +1710,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();
}
@@ -1782,6 +1868,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();
}
@@ -2072,6 +2163,25 @@ void OpenXRAPI::set_foveation_dynamic(bool p_foveation_dynamic) {
}
}
+Size2 OpenXRAPI::get_play_space_bounds() const {
+ Size2 ret;
+
+ ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Size2());
+
+ XrExtent2Df extents;
+
+ XrResult result = xrGetReferenceSpaceBoundsRect(session, reference_space, &extents);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: failed to get play space bounds! [", get_error_string(result), "]");
+ return ret;
+ }
+
+ ret.width = extents.width;
+ ret.height = extents.height;
+
+ return ret;
+}
+
OpenXRAPI::OpenXRAPI() {
// OpenXRAPI is only constructed if OpenXR is enabled.
singleton = this;
@@ -2116,10 +2226,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;
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 64769b244c..6e55020aef 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -98,7 +98,8 @@ 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
@@ -149,6 +150,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;
@@ -199,6 +204,7 @@ private:
EXT_PROTO_XRRESULT_FUNC3(xrGetActionStateVector2f, (XrSession), session, (const XrActionStateGetInfo *), getInfo, (XrActionStateVector2f *), state)
EXT_PROTO_XRRESULT_FUNC3(xrGetCurrentInteractionProfile, (XrSession), session, (XrPath), topLevelUserPath, (XrInteractionProfileState *), interactionProfile)
EXT_PROTO_XRRESULT_FUNC2(xrGetInstanceProperties, (XrInstance), instance, (XrInstanceProperties *), instanceProperties)
+ EXT_PROTO_XRRESULT_FUNC3(xrGetReferenceSpaceBoundsRect, (XrSession), session, (XrReferenceSpaceType), referenceSpaceType, (XrExtent2Df *), bounds)
EXT_PROTO_XRRESULT_FUNC3(xrGetSystem, (XrInstance), instance, (const XrSystemGetInfo *), getInfo, (XrSystemId *), systemId)
EXT_PROTO_XRRESULT_FUNC3(xrGetSystemProperties, (XrInstance), instance, (XrSystemId), systemId, (XrSystemProperties *), properties)
EXT_PROTO_XRRESULT_FUNC4(xrLocateSpace, (XrSpace), space, (XrSpace), baseSpace, (XrTime), time, (XrSpaceLocation *), location)
@@ -317,7 +323,7 @@ public:
XrResult try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr);
XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr);
- String get_error_string(XrResult result);
+ String get_error_string(XrResult result) const;
String get_swapchain_format_name(int64_t p_swapchain_format) const;
void set_xr_interface(OpenXRInterface *p_xr_interface);
@@ -332,7 +338,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);
+ void 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);
@@ -380,6 +387,9 @@ public:
bool get_foveation_dynamic() const;
void set_foveation_dynamic(bool p_foveation_dynamic);
+ // Play space.
+ Size2 get_play_space_bounds() const;
+
// action map
String get_default_action_map_resource_name();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 8ce76a5fad..6b311b73a8 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,15 +686,84 @@ 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) {
- return false;
+ ERR_FAIL_COND_V_MSG(initialized, false, "Cannot change play area mode after OpenXR interface has been initialized");
+ 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;
+ }
+
+ openxr_api->set_requested_reference_space(reference_space);
+ return true;
+}
+
+PackedVector3Array OpenXRInterface::get_play_area() const {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL_V(xr_server, PackedVector3Array());
+ PackedVector3Array arr;
+
+ Vector3 sides[4] = {
+ Vector3(-0.5f, 0.0f, -0.5f),
+ Vector3(0.5f, 0.0f, -0.5f),
+ Vector3(0.5f, 0.0f, 0.5f),
+ Vector3(-0.5f, 0.0f, 0.5f),
+ };
+
+ if (openxr_api != nullptr && openxr_api->is_initialized()) {
+ Size2 extents = openxr_api->get_play_space_bounds();
+ if (extents.width != 0.0 && extents.height != 0.0) {
+ Transform3D reference_frame = xr_server->get_reference_frame();
+
+ for (int i = 0; i < 4; i++) {
+ Vector3 coord = sides[i];
+
+ // Scale it up.
+ coord.x *= extents.width;
+ coord.z *= extents.height;
+
+ // Now apply our reference.
+ Vector3 out = reference_frame.xform(coord);
+ arr.push_back(out);
+ }
+ } else {
+ WARN_PRINT_ONCE("OpenXR: No extents available.");
+ }
+ }
+
+ return arr;
}
float OpenXRInterface::get_display_refresh_rate() const {
@@ -1233,6 +1309,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;
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 51ef4ea228..ca95fdf04d 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -125,6 +125,7 @@ public:
virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
virtual XRInterface::PlayAreaMode get_play_area_mode() const override;
virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
+ virtual PackedVector3Array get_play_area() const override;
float get_display_refresh_rate() const;
void set_display_refresh_rate(float p_refresh_rate);
@@ -193,6 +194,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,
@@ -247,6 +257,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/register_types.cpp b/modules/openxr/register_types.cpp
index 544932bdeb..04411a0c57 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -49,6 +49,8 @@
#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 +61,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,6 +108,7 @@ 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));
@@ -115,6 +118,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
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/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp
index 5b35a2643a..fba76621d6 100644
--- a/modules/squish/image_decompress_squish.cpp
+++ b/modules/squish/image_decompress_squish.cpp
@@ -36,7 +36,9 @@ void image_decompress_squish(Image *p_image) {
int w = p_image->get_width();
int h = p_image->get_height();
+ Image::Format source_format = p_image->get_format();
Image::Format target_format = Image::FORMAT_RGBA8;
+
Vector<uint8_t> data;
int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
int mm_count = p_image->get_mipmap_count();
@@ -45,33 +47,49 @@ void image_decompress_squish(Image *p_image) {
const uint8_t *rb = p_image->get_data().ptr();
uint8_t *wb = data.ptrw();
- int squish_flags = Image::FORMAT_MAX;
- if (p_image->get_format() == Image::FORMAT_DXT1) {
- squish_flags = squish::kDxt1;
- } else if (p_image->get_format() == Image::FORMAT_DXT3) {
- squish_flags = squish::kDxt3;
- } else if (p_image->get_format() == Image::FORMAT_DXT5 || p_image->get_format() == Image::FORMAT_DXT5_RA_AS_RG) {
- squish_flags = squish::kDxt5;
- } else if (p_image->get_format() == Image::FORMAT_RGTC_R) {
- squish_flags = squish::kBc4;
- } else if (p_image->get_format() == Image::FORMAT_RGTC_RG) {
- squish_flags = squish::kBc5;
- } else {
- ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
+ int squish_flags = 0;
+
+ switch (source_format) {
+ case Image::FORMAT_DXT1:
+ squish_flags = squish::kDxt1;
+ break;
+
+ case Image::FORMAT_DXT3:
+ squish_flags = squish::kDxt3;
+ break;
+
+ case Image::FORMAT_DXT5:
+ case Image::FORMAT_DXT5_RA_AS_RG:
+ squish_flags = squish::kDxt5;
+ break;
+
+ case Image::FORMAT_RGTC_R:
+ squish_flags = squish::kBc4;
+ break;
+
+ case Image::FORMAT_RGTC_RG:
+ squish_flags = squish::kBc5;
+ break;
+
+ default:
+ ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
+ break;
}
for (int i = 0; i <= mm_count; i++) {
int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0;
p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
+
int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags);
+
w >>= 1;
h >>= 1;
}
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
- if (p_image->get_format() == Image::FORMAT_DXT5_RA_AS_RG) {
+ if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
p_image->convert_ra_rgba8_to_rg();
}
}
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/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/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py
index 3c5229462c..e58bc3abec 100644
--- a/modules/text_server_adv/gdextension_build/methods.py
+++ b/modules/text_server_adv/gdextension_build/methods.py
@@ -99,8 +99,8 @@ def make_icu_data(target, source, env):
def write_macos_plist(target, binary_name, identifier, name):
- os.makedirs(f"{target}/Resourece/", exist_ok=True)
- f = open(f"{target}/Resourece/Info.plist", "w")
+ os.makedirs(f"{target}/Resource/", exist_ok=True)
+ f = open(f"{target}/Resource/Info.plist", "w")
f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
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 eaab04b49a..b4028492c7 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
}
@@ -1113,14 +1113,14 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
case FT_PIXEL_MODE_LCD: {
int ofs_color = i * bitmap.pitch + (j * 3);
if (p_bgra) {
- wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
- wr[ofs + 2] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
wr[ofs + 3] = 255;
} else {
- wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
- wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 2];
wr[ofs + 3] = 255;
}
} break;
@@ -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);
@@ -3504,6 +3535,9 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
+ if (p_index == 0) {
+ return; // Non visual character, skip.
+ }
FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -3541,6 +3575,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
+ if (gl.uv_rect.size.x <= 2 || gl.uv_rect.size.y <= 2) {
+ return; // Nothing to draw.
+ }
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
@@ -3608,6 +3645,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
+ if (p_index == 0) {
+ return; // Non visual character, skip.
+ }
FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -3645,6 +3685,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
+ if (gl.uv_rect.size.x <= 2 || gl.uv_rect.size.y <= 2) {
+ return; // Nothing to draw.
+ }
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
@@ -4048,6 +4091,20 @@ String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shap
return sd->custom_punct;
}
+void TextServerAdvanced::_shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL(sd);
+ sd->el_char = p_char;
+}
+
+int64_t TextServerAdvanced::_shaped_text_get_custom_ellipsis(const RID &p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL_V(sd, 0);
+ return sd->el_char;
+}
+
void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd);
@@ -4800,6 +4857,166 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
return 0.0;
}
+RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text) {
+ RID f;
+ // Try system fallback.
+ String font_name = _font_get_name(p_fdef);
+ BitField<FontStyle> font_style = _font_get_style(p_fdef);
+ int font_weight = _font_get_weight(p_fdef);
+ int font_stretch = _font_get_stretch(p_fdef);
+ Dictionary dvar = _font_get_variation_coordinates(p_fdef);
+ static int64_t wgth_tag = _name_to_tag("weight");
+ static int64_t wdth_tag = _name_to_tag("width");
+ static int64_t ital_tag = _name_to_tag("italic");
+ if (dvar.has(wgth_tag)) {
+ font_weight = dvar[wgth_tag].operator int();
+ }
+ if (dvar.has(wdth_tag)) {
+ font_stretch = dvar[wdth_tag].operator int();
+ }
+ if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
+ font_style.set_flag(TextServer::FONT_ITALIC);
+ }
+
+ String locale = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
+ PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, p_text, locale, p_script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
+#ifdef GDEXTENSION
+ for (int fb = 0; fb < fallback_font_name.size(); fb++) {
+ const String &E = fallback_font_name[fb];
+#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);
+ if (system_fonts.has(key)) {
+ const SystemFontCache &sysf_cache = system_fonts[key];
+ int best_score = 0;
+ int best_match = -1;
+ for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
+ const SystemFontCacheRec &F = sysf_cache.var[face_idx];
+ if (unlikely(!_font_has_char(F.rid, p_text[0]))) {
+ continue;
+ }
+ BitField<FontStyle> style = _font_get_style(F.rid);
+ int weight = _font_get_weight(F.rid);
+ int stretch = _font_get_stretch(F.rid);
+ int score = (20 - Math::abs(weight - font_weight) / 50);
+ score += (20 - Math::abs(stretch - font_stretch) / 10);
+ if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
+ score += 30;
+ }
+ if (score >= best_score) {
+ best_score = score;
+ best_match = face_idx;
+ }
+ if (best_score == 70) {
+ break;
+ }
+ }
+ if (best_match != -1) {
+ f = sysf_cache.var[best_match].rid;
+ }
+ }
+ if (!f.is_valid()) {
+ if (system_fonts.has(key)) {
+ const SystemFontCache &sysf_cache = system_fonts[key];
+ if (sysf_cache.max_var == sysf_cache.var.size()) {
+ // All subfonts already tested, skip.
+ continue;
+ }
+ }
+
+ if (!system_font_data.has(E)) {
+ system_font_data[E] = FileAccess::get_file_as_bytes(E);
+ }
+
+ const PackedByteArray &font_data = system_font_data[E];
+
+ SystemFontCacheRec sysf;
+ sysf.rid = _create_font();
+ _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
+
+ Dictionary var = dvar;
+ // Select matching style from collection.
+ int best_score = 0;
+ int best_match = -1;
+ for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
+ _font_set_face_index(sysf.rid, face_idx);
+ if (unlikely(!_font_has_char(sysf.rid, p_text[0]))) {
+ continue;
+ }
+ BitField<FontStyle> style = _font_get_style(sysf.rid);
+ int weight = _font_get_weight(sysf.rid);
+ int stretch = _font_get_stretch(sysf.rid);
+ int score = (20 - Math::abs(weight - font_weight) / 50);
+ score += (20 - Math::abs(stretch - font_stretch) / 10);
+ if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
+ score += 30;
+ }
+ if (score >= best_score) {
+ best_score = score;
+ best_match = face_idx;
+ }
+ if (best_score == 70) {
+ break;
+ }
+ }
+ if (best_match == -1) {
+ _free_rid(sysf.rid);
+ continue;
+ } else {
+ _font_set_face_index(sysf.rid, best_match);
+ }
+ sysf.index = best_match;
+
+ // If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
+ if (best_score != 70) {
+ Dictionary ftr = _font_supported_variation_list(sysf.rid);
+ if (ftr.has(wdth_tag)) {
+ var[wdth_tag] = font_stretch;
+ _font_set_stretch(sysf.rid, font_stretch);
+ }
+ if (ftr.has(wgth_tag)) {
+ var[wgth_tag] = font_weight;
+ _font_set_weight(sysf.rid, font_weight);
+ }
+ if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
+ var[ital_tag] = 1;
+ _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
+ }
+ }
+
+ _font_set_antialiasing(sysf.rid, key.antialiasing);
+ _font_set_generate_mipmaps(sysf.rid, key.mipmaps);
+ _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
+ _font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
+ _font_set_msdf_size(sysf.rid, key.msdf_source_size);
+ _font_set_fixed_size(sysf.rid, key.fixed_size);
+ _font_set_force_autohinter(sysf.rid, key.force_autohinter);
+ _font_set_hinting(sysf.rid, key.hinting);
+ _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
+ _font_set_variation_coordinates(sysf.rid, var);
+ _font_set_oversampling(sysf.rid, key.oversampling);
+ _font_set_embolden(sysf.rid, key.embolden);
+ _font_set_transform(sysf.rid, key.transform);
+ _font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
+ _font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
+ _font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
+ _font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
+
+ if (system_fonts.has(key)) {
+ system_fonts[key].var.push_back(sysf);
+ } else {
+ SystemFontCache &sysf_cache = system_fonts[key];
+ sysf_cache.max_var = _font_get_face_count(sysf.rid);
+ sysf_cache.var.push_back(sysf);
+ }
+ f = sysf.rid;
+ }
+ break;
+ }
+ return f;
+}
+
void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
@@ -4842,20 +5059,52 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int sd_size = sd->glyphs.size();
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
+ bool found_el_char = false;
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
+ found_el_char = true;
break;
}
}
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
+ }
+ }
}
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(whitespace_gl_font_rid, '.')) {
+ if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], ' ')) {
@@ -4865,14 +5114,14 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.', 0) : -10;
+ int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
- int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -10;
+ int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
- ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
+ ellipsis_width = (found_el_char ? 1 : 3) * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
}
int ell_min_characters = 6;
@@ -4951,7 +5200,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if (dot_gl_idx != 0) {
Glyph gl;
gl.count = 1;
- gl.repeat = 3;
+ gl.repeat = (found_el_char ? 1 : 3);
gl.advance = dot_adv.x;
gl.index = dot_gl_idx;
gl.font_rid = dot_gl_font_rid;
@@ -5532,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;
@@ -5580,166 +5834,12 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
break;
}
}
- String text = p_sd->text.substr(p_start, next - p_start);
-
- String font_name = _font_get_name(fdef);
- BitField<FontStyle> font_style = _font_get_style(fdef);
- int font_weight = _font_get_weight(fdef);
- int font_stretch = _font_get_stretch(fdef);
- Dictionary dvar = _font_get_variation_coordinates(fdef);
- static int64_t wgth_tag = _name_to_tag("weight");
- static int64_t wdth_tag = _name_to_tag("width");
- static int64_t ital_tag = _name_to_tag("italic");
- if (dvar.has(wgth_tag)) {
- font_weight = dvar[wgth_tag].operator int();
- }
- if (dvar.has(wdth_tag)) {
- font_stretch = dvar[wdth_tag].operator int();
- }
- if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
- font_style.set_flag(TextServer::FONT_ITALIC);
- }
-
char scr_buffer[5] = { 0, 0, 0, 0, 0 };
hb_tag_to_string(hb_script_to_iso15924_tag(p_script), scr_buffer);
String script_code = String(scr_buffer);
- String locale = (p_sd->spans[p_span].language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_sd->spans[p_span].language;
- PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
-#ifdef GDEXTENSION
- for (int fb = 0; fb < fallback_font_name.size(); fb++) {
- const String &E = fallback_font_name[fb];
-#else
- for (const String &E : fallback_font_name) {
-#endif
- SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this);
- if (system_fonts.has(key)) {
- const SystemFontCache &sysf_cache = system_fonts[key];
- int best_score = 0;
- int best_match = -1;
- for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
- const SystemFontCacheRec &F = sysf_cache.var[face_idx];
- if (unlikely(!_font_has_char(F.rid, text[0]))) {
- continue;
- }
- BitField<FontStyle> style = _font_get_style(F.rid);
- int weight = _font_get_weight(F.rid);
- int stretch = _font_get_stretch(F.rid);
- int score = (20 - Math::abs(weight - font_weight) / 50);
- score += (20 - Math::abs(stretch - font_stretch) / 10);
- if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
- score += 30;
- }
- if (score >= best_score) {
- best_score = score;
- best_match = face_idx;
- }
- if (best_score == 70) {
- break;
- }
- }
- if (best_match != -1) {
- f = sysf_cache.var[best_match].rid;
- }
- }
- if (!f.is_valid()) {
- if (system_fonts.has(key)) {
- const SystemFontCache &sysf_cache = system_fonts[key];
- if (sysf_cache.max_var == sysf_cache.var.size()) {
- // All subfonts already tested, skip.
- continue;
- }
- }
-
- if (!system_font_data.has(E)) {
- system_font_data[E] = FileAccess::get_file_as_bytes(E);
- }
-
- const PackedByteArray &font_data = system_font_data[E];
-
- SystemFontCacheRec sysf;
- sysf.rid = _create_font();
- _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
-
- Dictionary var = dvar;
- // Select matching style from collection.
- int best_score = 0;
- int best_match = -1;
- for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
- _font_set_face_index(sysf.rid, face_idx);
- if (unlikely(!_font_has_char(sysf.rid, text[0]))) {
- continue;
- }
- BitField<FontStyle> style = _font_get_style(sysf.rid);
- int weight = _font_get_weight(sysf.rid);
- int stretch = _font_get_stretch(sysf.rid);
- int score = (20 - Math::abs(weight - font_weight) / 50);
- score += (20 - Math::abs(stretch - font_stretch) / 10);
- if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
- score += 30;
- }
- if (score >= best_score) {
- best_score = score;
- best_match = face_idx;
- }
- if (best_score == 70) {
- break;
- }
- }
- if (best_match == -1) {
- _free_rid(sysf.rid);
- continue;
- } else {
- _font_set_face_index(sysf.rid, best_match);
- }
- sysf.index = best_match;
-
- // If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
- if (best_score != 70) {
- Dictionary ftr = _font_supported_variation_list(sysf.rid);
- if (ftr.has(wdth_tag)) {
- var[wdth_tag] = font_stretch;
- _font_set_stretch(sysf.rid, font_stretch);
- }
- if (ftr.has(wgth_tag)) {
- var[wgth_tag] = font_weight;
- _font_set_weight(sysf.rid, font_weight);
- }
- if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
- var[ital_tag] = 1;
- _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
- }
- }
-
- _font_set_antialiasing(sysf.rid, key.antialiasing);
- _font_set_generate_mipmaps(sysf.rid, key.mipmaps);
- _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
- _font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
- _font_set_msdf_size(sysf.rid, key.msdf_source_size);
- _font_set_fixed_size(sysf.rid, key.fixed_size);
- _font_set_force_autohinter(sysf.rid, key.force_autohinter);
- _font_set_hinting(sysf.rid, key.hinting);
- _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
- _font_set_variation_coordinates(sysf.rid, var);
- _font_set_oversampling(sysf.rid, key.oversampling);
- _font_set_embolden(sysf.rid, key.embolden);
- _font_set_transform(sysf.rid, key.transform);
- _font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
- _font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
- _font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
- _font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
-
- if (system_fonts.has(key)) {
- system_fonts[key].var.push_back(sysf);
- } else {
- SystemFontCache &sysf_cache = system_fonts[key];
- sysf_cache.max_var = _font_get_face_count(sysf.rid);
- sysf_cache.var.push_back(sysf);
- }
- f = sysf.rid;
- }
- break;
- }
+ String text = p_sd->text.substr(p_start, next - p_start);
+ f = _find_sys_font_for_text(fdef, script_code, p_sd->spans[p_span].language, text);
}
}
@@ -5900,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.
@@ -6693,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 cbd2911aaf..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;
@@ -502,6 +504,7 @@ class TextServerAdvanced : public TextServerExtension {
double upos = 0.0;
double uthk = 0.0;
+ char32_t el_char = 0x2026;
TrimData overrun_trim_data;
bool fit_width_minimum_reached = false;
@@ -573,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) {
@@ -600,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);
}
};
@@ -632,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));
}
};
@@ -647,6 +652,7 @@ class TextServerAdvanced : public TextServerExtension {
bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const;
void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end);
Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size);
+ _FORCE_INLINE_ RID _find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text);
_FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs);
@@ -779,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 &);
@@ -899,6 +908,9 @@ public:
MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &);
MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &);
+ MODBIND2(shaped_text_set_custom_ellipsis, const RID &, int64_t);
+ MODBIND1RC(int64_t, shaped_text_get_custom_ellipsis, const RID &);
+
MODBIND2(shaped_text_set_orientation, const RID &, Orientation);
MODBIND1RC(Orientation, shaped_text_get_orientation, 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/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py
index 3c5229462c..e58bc3abec 100644
--- a/modules/text_server_fb/gdextension_build/methods.py
+++ b/modules/text_server_fb/gdextension_build/methods.py
@@ -99,8 +99,8 @@ def make_icu_data(target, source, env):
def write_macos_plist(target, binary_name, identifier, name):
- os.makedirs(f"{target}/Resourece/", exist_ok=True)
- f = open(f"{target}/Resourece/Info.plist", "w")
+ os.makedirs(f"{target}/Resource/", exist_ok=True)
+ f = open(f"{target}/Resource/Info.plist", "w")
f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
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 eb247cdcbe..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
}
@@ -548,14 +548,14 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
case FT_PIXEL_MODE_LCD: {
int ofs_color = i * bitmap.pitch + (j * 3);
if (p_bgra) {
- wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
- wr[ofs + 2] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
wr[ofs + 3] = 255;
} else {
- wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
- wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 2];
wr[ofs + 3] = 255;
}
} break;
@@ -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);
@@ -2439,6 +2470,9 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
+ if (p_index == 0) {
+ return; // Non visual character, skip.
+ }
FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -2476,6 +2510,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
+ if (gl.uv_rect.size.x <= 2 || gl.uv_rect.size.y <= 2) {
+ return; // Nothing to draw.
+ }
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
@@ -2543,6 +2580,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
+ if (p_index == 0) {
+ return; // Non visual character, skip.
+ }
FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -2580,6 +2620,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
+ if (gl.uv_rect.size.x <= 2 || gl.uv_rect.size.y <= 2) {
+ return; // Nothing to draw.
+ }
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
@@ -2905,6 +2948,20 @@ String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shap
return sd->custom_punct;
}
+void TextServerFallback::_shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL(sd);
+ sd->el_char = p_char;
+}
+
+int64_t TextServerFallback::_shaped_text_get_custom_ellipsis(const RID &p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL_V(sd, 0);
+ return sd->el_char;
+}
+
void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd);
@@ -3601,6 +3658,168 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
return true;
}
+RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text) {
+ RID f;
+ // Try system fallback.
+ if (_font_is_allow_system_fallback(p_fdef)) {
+ String font_name = _font_get_name(p_fdef);
+ BitField<FontStyle> font_style = _font_get_style(p_fdef);
+ int font_weight = _font_get_weight(p_fdef);
+ int font_stretch = _font_get_stretch(p_fdef);
+ Dictionary dvar = _font_get_variation_coordinates(p_fdef);
+ static int64_t wgth_tag = _name_to_tag("weight");
+ static int64_t wdth_tag = _name_to_tag("width");
+ static int64_t ital_tag = _name_to_tag("italic");
+ if (dvar.has(wgth_tag)) {
+ font_weight = dvar[wgth_tag].operator int();
+ }
+ if (dvar.has(wdth_tag)) {
+ font_stretch = dvar[wdth_tag].operator int();
+ }
+ if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
+ font_style.set_flag(TextServer::FONT_ITALIC);
+ }
+
+ String locale = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
+ PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, p_text, locale, p_script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
+#ifdef GDEXTENSION
+ for (int fb = 0; fb < fallback_font_name.size(); fb++) {
+ const String &E = fallback_font_name[fb];
+#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);
+ if (system_fonts.has(key)) {
+ const SystemFontCache &sysf_cache = system_fonts[key];
+ int best_score = 0;
+ int best_match = -1;
+ for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
+ const SystemFontCacheRec &F = sysf_cache.var[face_idx];
+ if (unlikely(!_font_has_char(F.rid, p_text[0]))) {
+ continue;
+ }
+ BitField<FontStyle> style = _font_get_style(F.rid);
+ int weight = _font_get_weight(F.rid);
+ int stretch = _font_get_stretch(F.rid);
+ int score = (20 - Math::abs(weight - font_weight) / 50);
+ score += (20 - Math::abs(stretch - font_stretch) / 10);
+ if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
+ score += 30;
+ }
+ if (score >= best_score) {
+ best_score = score;
+ best_match = face_idx;
+ }
+ if (best_score == 70) {
+ break;
+ }
+ }
+ if (best_match != -1) {
+ f = sysf_cache.var[best_match].rid;
+ }
+ }
+ if (!f.is_valid()) {
+ if (system_fonts.has(key)) {
+ const SystemFontCache &sysf_cache = system_fonts[key];
+ if (sysf_cache.max_var == sysf_cache.var.size()) {
+ // All subfonts already tested, skip.
+ continue;
+ }
+ }
+
+ if (!system_font_data.has(E)) {
+ system_font_data[E] = FileAccess::get_file_as_bytes(E);
+ }
+
+ const PackedByteArray &font_data = system_font_data[E];
+
+ SystemFontCacheRec sysf;
+ sysf.rid = _create_font();
+ _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
+
+ Dictionary var = dvar;
+ // Select matching style from collection.
+ int best_score = 0;
+ int best_match = -1;
+ for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
+ _font_set_face_index(sysf.rid, face_idx);
+ if (unlikely(!_font_has_char(sysf.rid, p_text[0]))) {
+ continue;
+ }
+ BitField<FontStyle> style = _font_get_style(sysf.rid);
+ int weight = _font_get_weight(sysf.rid);
+ int stretch = _font_get_stretch(sysf.rid);
+ int score = (20 - Math::abs(weight - font_weight) / 50);
+ score += (20 - Math::abs(stretch - font_stretch) / 10);
+ if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
+ score += 30;
+ }
+ if (score >= best_score) {
+ best_score = score;
+ best_match = face_idx;
+ }
+ if (best_score == 70) {
+ break;
+ }
+ }
+ if (best_match == -1) {
+ _free_rid(sysf.rid);
+ continue;
+ } else {
+ _font_set_face_index(sysf.rid, best_match);
+ }
+ sysf.index = best_match;
+
+ // If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
+ if (best_score != 70) {
+ Dictionary ftr = _font_supported_variation_list(sysf.rid);
+ if (ftr.has(wdth_tag)) {
+ var[wdth_tag] = font_stretch;
+ _font_set_stretch(sysf.rid, font_stretch);
+ }
+ if (ftr.has(wgth_tag)) {
+ var[wgth_tag] = font_weight;
+ _font_set_weight(sysf.rid, font_weight);
+ }
+ if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
+ var[ital_tag] = 1;
+ _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
+ }
+ }
+
+ _font_set_antialiasing(sysf.rid, key.antialiasing);
+ _font_set_generate_mipmaps(sysf.rid, key.mipmaps);
+ _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
+ _font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
+ _font_set_msdf_size(sysf.rid, key.msdf_source_size);
+ _font_set_fixed_size(sysf.rid, key.fixed_size);
+ _font_set_force_autohinter(sysf.rid, key.force_autohinter);
+ _font_set_hinting(sysf.rid, key.hinting);
+ _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
+ _font_set_variation_coordinates(sysf.rid, var);
+ _font_set_oversampling(sysf.rid, key.oversampling);
+ _font_set_embolden(sysf.rid, key.embolden);
+ _font_set_transform(sysf.rid, key.transform);
+ _font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
+ _font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
+ _font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
+ _font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
+
+ if (system_fonts.has(key)) {
+ system_fonts[key].var.push_back(sysf);
+ } else {
+ SystemFontCache &sysf_cache = system_fonts[key];
+ sysf_cache.max_var = _font_get_face_count(sysf.rid);
+ sysf_cache.var.push_back(sysf);
+ }
+ f = sysf.rid;
+ }
+ break;
+ }
+ }
+ return f;
+}
+
void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
@@ -3643,20 +3862,52 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int sd_size = sd->glyphs.size();
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
+ bool found_el_char = false;
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
+ found_el_char = true;
break;
}
}
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
+ }
+ }
}
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(whitespace_gl_font_rid, '.')) {
+ if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], ' ')) {
@@ -3666,14 +3917,14 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.', 0) : -10;
+ int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
- int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -10;
+ int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
- ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
+ ellipsis_width = (found_el_char ? 1 : 3) * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
}
int ell_min_characters = 6;
@@ -3742,7 +3993,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if (dot_gl_idx != 0) {
Glyph gl;
gl.count = 1;
- gl.repeat = 3;
+ gl.repeat = (found_el_char ? 1 : 3);
gl.advance = dot_adv.x;
gl.index = dot_gl_idx;
gl.font_rid = dot_gl_font_rid;
@@ -3873,161 +4124,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
RID fdef = span.fonts[0];
if (_font_is_allow_system_fallback(fdef)) {
String text = sd->text.substr(j, 1);
- String font_name = _font_get_name(fdef);
- BitField<FontStyle> font_style = _font_get_style(fdef);
- int font_weight = _font_get_weight(fdef);
- int font_stretch = _font_get_stretch(fdef);
- Dictionary dvar = _font_get_variation_coordinates(fdef);
- static int64_t wgth_tag = _name_to_tag("weight");
- static int64_t wdth_tag = _name_to_tag("width");
- static int64_t ital_tag = _name_to_tag("italic");
- if (dvar.has(wgth_tag)) {
- font_weight = dvar[wgth_tag].operator int();
- }
- if (dvar.has(wdth_tag)) {
- font_stretch = dvar[wdth_tag].operator int();
- }
- if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
- font_style.set_flag(TextServer::FONT_ITALIC);
- }
-
- String locale = (span.language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : span.language;
-
- PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, String(), font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
-#ifdef GDEXTENSION
- for (int fb = 0; fb < fallback_font_name.size(); fb++) {
- const String &E = fallback_font_name[fb];
-#else
- for (const String &E : fallback_font_name) {
-#endif
- SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this);
- if (system_fonts.has(key)) {
- const SystemFontCache &sysf_cache = system_fonts[key];
- int best_score = 0;
- int best_match = -1;
- for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
- const SystemFontCacheRec &F = sysf_cache.var[face_idx];
- if (unlikely(!_font_has_char(F.rid, text[0]))) {
- continue;
- }
- BitField<FontStyle> style = _font_get_style(F.rid);
- int weight = _font_get_weight(F.rid);
- int stretch = _font_get_stretch(F.rid);
- int score = (20 - Math::abs(weight - font_weight) / 50);
- score += (20 - Math::abs(stretch - font_stretch) / 10);
- if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
- score += 30;
- }
- if (score >= best_score) {
- best_score = score;
- best_match = face_idx;
- }
- if (best_score == 70) {
- break;
- }
- }
- if (best_match != -1) {
- gl.font_rid = sysf_cache.var[best_match].rid;
- }
- }
- if (!gl.font_rid.is_valid()) {
- if (system_fonts.has(key)) {
- const SystemFontCache &sysf_cache = system_fonts[key];
- if (sysf_cache.max_var == sysf_cache.var.size()) {
- // All subfonts already tested, skip.
- continue;
- }
- }
-
- if (!system_font_data.has(E)) {
- system_font_data[E] = FileAccess::get_file_as_bytes(E);
- }
-
- const PackedByteArray &font_data = system_font_data[E];
-
- SystemFontCacheRec sysf;
- sysf.rid = _create_font();
- _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
-
- Dictionary var = dvar;
- // Select matching style from collection.
- int best_score = 0;
- int best_match = -1;
- for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
- _font_set_face_index(sysf.rid, face_idx);
- if (unlikely(!_font_has_char(sysf.rid, text[0]))) {
- continue;
- }
- BitField<FontStyle> style = _font_get_style(sysf.rid);
- int weight = _font_get_weight(sysf.rid);
- int stretch = _font_get_stretch(sysf.rid);
- int score = (20 - Math::abs(weight - font_weight) / 50);
- score += (20 - Math::abs(stretch - font_stretch) / 10);
- if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
- score += 30;
- }
- if (score >= best_score) {
- best_score = score;
- best_match = face_idx;
- }
- if (best_score == 70) {
- break;
- }
- }
- if (best_match == -1) {
- _free_rid(sysf.rid);
- continue;
- } else {
- _font_set_face_index(sysf.rid, best_match);
- }
- sysf.index = best_match;
-
- // If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
- if (best_score != 70) {
- Dictionary ftr = _font_supported_variation_list(sysf.rid);
- if (ftr.has(wdth_tag)) {
- var[wdth_tag] = font_stretch;
- _font_set_stretch(sysf.rid, font_stretch);
- }
- if (ftr.has(wgth_tag)) {
- var[wgth_tag] = font_weight;
- _font_set_weight(sysf.rid, font_weight);
- }
- if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
- var[ital_tag] = 1;
- _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
- }
- }
-
- _font_set_antialiasing(sysf.rid, key.antialiasing);
- _font_set_generate_mipmaps(sysf.rid, key.mipmaps);
- _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
- _font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
- _font_set_msdf_size(sysf.rid, key.msdf_source_size);
- _font_set_fixed_size(sysf.rid, key.fixed_size);
- _font_set_force_autohinter(sysf.rid, key.force_autohinter);
- _font_set_hinting(sysf.rid, key.hinting);
- _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
- _font_set_variation_coordinates(sysf.rid, var);
- _font_set_oversampling(sysf.rid, key.oversampling);
- _font_set_embolden(sysf.rid, key.embolden);
- _font_set_transform(sysf.rid, key.transform);
- _font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
- _font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
- _font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
- _font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
-
- if (system_fonts.has(key)) {
- system_fonts[key].var.push_back(sysf);
- } else {
- SystemFontCache &sysf_cache = system_fonts[key];
- sysf_cache.max_var = _font_get_face_count(sysf.rid);
- sysf_cache.var.push_back(sysf);
- }
- gl.font_rid = sysf.rid;
- }
- break;
- }
+ gl.font_rid = _find_sys_font_for_text(fdef, String(), span.language, text);
}
}
prev_font = gl.font_rid;
@@ -4039,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 5c30ea0c05..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;
@@ -447,6 +449,7 @@ class TextServerFallback : public TextServerExtension {
double upos = 0.0;
double uthk = 0.0;
+ char32_t el_char = 0x2026;
TrimData overrun_trim_data;
bool fit_width_minimum_reached = false;
@@ -489,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) {
@@ -516,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);
}
};
@@ -548,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));
}
};
@@ -555,6 +561,7 @@ class TextServerFallback : public TextServerExtension {
mutable HashMap<String, PackedByteArray> system_font_data;
void _realign(ShapedTextDataFallback *p_sd) const;
+ _FORCE_INLINE_ RID _find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text);
Mutex ft_mutex;
@@ -646,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 &);
@@ -766,6 +776,9 @@ public:
MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &);
MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &);
+ MODBIND2(shaped_text_set_custom_ellipsis, const RID &, int64_t);
+ MODBIND1RC(int64_t, shaped_text_get_custom_ellipsis, const RID &);
+
MODBIND2(shaped_text_set_orientation, const RID &, Orientation);
MODBIND1RC(Orientation, shaped_text_get_orientation, 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/tinyexr/image_loader_tinyexr.cpp b/modules/tinyexr/image_loader_tinyexr.cpp
index f64bb14e4a..8720ca56f6 100644
--- a/modules/tinyexr/image_loader_tinyexr.cpp
+++ b/modules/tinyexr/image_loader_tinyexr.cpp
@@ -68,6 +68,7 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitF
if (ret != TINYEXR_SUCCESS) {
if (err) {
ERR_PRINT(String(err));
+ FreeEXRErrorMessage(err);
}
return ERR_FILE_CORRUPT;
}
@@ -86,6 +87,7 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitF
if (ret != TINYEXR_SUCCESS) {
if (err) {
ERR_PRINT(String(err));
+ FreeEXRErrorMessage(err);
}
return ERR_FILE_CORRUPT;
}
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 8a265ffaf3..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);
@@ -144,7 +145,7 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
}
int AudioStreamPlaybackOggVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p_frames) {
- ERR_FAIL_COND_V(!ready, 0);
+ ERR_FAIL_COND_V(!ready, p_frames);
if (!have_samples_left) {
ogg_packet *packet = nullptr;
int err;
@@ -156,10 +157,10 @@ int AudioStreamPlaybackOggVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p
}
err = vorbis_synthesis(&block, packet);
- ERR_FAIL_COND_V_MSG(err != 0, 0, "Error during vorbis synthesis " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != 0, p_frames, "Error during vorbis synthesis " + itos(err));
err = vorbis_synthesis_blockin(&dsp_state, &block);
- ERR_FAIL_COND_V_MSG(err != 0, 0, "Error during vorbis block processing " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != 0, p_frames, "Error during vorbis block processing " + itos(err));
have_packets_left = !packet->e_o_s;
}
@@ -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/vorbis/doc_classes/AudioStreamOggVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
index 7e3af6688a..d2f6745e2f 100644
--- a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
+++ b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
@@ -7,6 +7,7 @@
The AudioStreamOggVorbis class is a specialized [AudioStream] for handling Ogg Vorbis file formats. It offers functionality for loading and playing back Ogg Vorbis files, as well as managing looping and other playback properties. This class is part of the audio stream system, which also supports WAV files through the [AudioStreamWAV] class.
</description>
<tutorials>
+ <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="load_from_buffer" qualifiers="static">
diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp
index 26af912999..def34220ea 100644
--- a/modules/vorbis/register_types.cpp
+++ b/modules/vorbis/register_types.cpp
@@ -48,8 +48,13 @@ void initialize_vorbis_module(ModuleInitializationLevel p_level) {
ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer);
}
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
// Required to document import options in the class reference.
GDREGISTER_CLASS(ResourceImporterOggVorbis);
+
+ ClassDB::set_current_api(prev_api);
#endif
GDREGISTER_CLASS(AudioStreamOggVorbis);
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
index a8c92f06f6..bf5d964d39 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -90,7 +90,7 @@ bool ResourceImporterOggVorbis::has_advanced_options() const {
void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) {
Ref<AudioStreamOggVorbis> ogg_stream = load_from_file(p_path);
if (ogg_stream.is_valid()) {
- AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream);
+ AudioStreamImportSettingsDialog::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream);
}
}
#endif
diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp
index bc34a25733..3a2ac5a90e 100644
--- a/modules/webp/webp_common.cpp
+++ b/modules/webp/webp_common.cpp
@@ -59,6 +59,10 @@ Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_
compression_method = CLAMP(compression_method, 0, 6);
Ref<Image> img = p_image->duplicate();
+ if (img->is_compressed()) {
+ Error error = img->decompress();
+ ERR_FAIL_COND_V_MSG(error != OK, Vector<uint8_t>(), "Couldn't decompress image.");
+ }
if (img->detect_alpha()) {
img->convert(Image::FORMAT_RGBA8);
} else {
diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
index 454f8f2ed4..8698c5755a 100644
--- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
+++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
@@ -76,7 +76,7 @@
<method name="get_signaling_state" qualifiers="const">
<return type="int" enum="WebRTCPeerConnection.SignalingState" />
<description>
- Returns the [enum SignalingState] on the local end of the connection while connecting or reconnecting to another peer.
+ Returns the signaling state on the local end of the connection while connecting or reconnecting to another peer.
</description>
</method>
<method name="initialize">
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 47f20ce1a3..828476edfb 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -309,7 +309,7 @@ void WebXRInterfaceJS::uninitialize() {
godot_webxr_uninitialize();
- GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
+ GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage != nullptr) {
for (KeyValue<unsigned int, RID> &E : texture_cache) {
// Forcibly mark as not part of a render target so we can free it.
@@ -438,16 +438,11 @@ Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_a
}
bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) {
- GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
+ GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage == nullptr) {
return false;
}
- GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target);
- if (rt == nullptr) {
- return false;
- }
-
// Cache the resources so we don't have to get them from JS twice.
color_texture = _get_color_texture();
depth_texture = _get_depth_texture();
@@ -460,23 +455,9 @@ bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) {
//
// See: https://immersive-web.github.io/layers/#xropaquetextures
//
- // This is why we're doing this sort of silly check: if the color and depth
- // textures are the same this frame as last frame, we need to attach them
- // again, despite the fact that the GLuint for them hasn't changed.
- if (rt->overridden.is_overridden && rt->overridden.color == color_texture && rt->overridden.depth == depth_texture) {
- GLES3::Config *config = GLES3::Config::get_singleton();
- bool use_multiview = rt->view_count > 1 && config->multiview_supported;
-
- glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo);
- if (use_multiview) {
- glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count);
- glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count);
- } else {
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->color, 0);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0);
- }
- glBindFramebuffer(GL_FRAMEBUFFER, texture_storage->system_fbo);
- }
+ // So, even if the color and depth textures have the same GLuint as the last
+ // frame, we need to re-attach them again.
+ texture_storage->render_target_set_reattach_textures(p_render_target, true);
return true;
}
@@ -484,7 +465,12 @@ bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) {
Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
Vector<BlitToScreen> blit_to_screen;
- // We don't need to do anything here.
+ GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
+ if (texture_storage == nullptr) {
+ return blit_to_screen;
+ }
+
+ texture_storage->render_target_set_reattach_textures(p_render_target, false);
return blit_to_screen;
};
@@ -513,7 +499,7 @@ RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) {
return cache->get();
}
- GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
+ GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage == nullptr) {
return RID();
}
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index 5f623476fc..e96d9da7a9 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -72,7 +72,24 @@ Error ZIPPacker::start_file(const String &p_path) {
zipfi.internal_fa = 0;
zipfi.external_fa = 0;
- int err = zipOpenNewFileInZip(zf, p_path.utf8().get_data(), &zipfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
+ int err = zipOpenNewFileInZip4(zf,
+ p_path.utf8().get_data(),
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION,
+ 0,
+ -MAX_WBITS,
+ DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY,
+ nullptr,
+ 0,
+ 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;
}