summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/basis_universal/register_types.cpp2
-rw-r--r--modules/csg/csg_shape.cpp8
-rw-r--r--modules/csg/doc_classes/CSGMesh3D.xml3
-rw-r--r--modules/csg/editor/csg_gizmos.cpp72
-rw-r--r--modules/csg/editor/csg_gizmos.h6
-rw-r--r--modules/csg/icons/CSGTorus3D.svg2
-rw-r--r--modules/dds/image_loader_dds.cpp422
-rw-r--r--modules/dds/register_types.cpp8
-rw-r--r--modules/dds/texture_loader_dds.cpp361
-rw-r--r--modules/denoise/SCsub129
-rw-r--r--modules/denoise/config.py12
-rw-r--r--modules/denoise/denoise_wrapper.h38
-rw-r--r--modules/denoise/resource_to_cpp.py68
-rw-r--r--modules/enet/enet_connection.cpp46
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp29
-rw-r--r--modules/enet/enet_packet_peer.cpp36
-rw-r--r--modules/freetype/SCsub6
-rw-r--r--modules/freetype/uwpdef.h38
-rw-r--r--modules/gdscript/.editorconfig8
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml9
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp200
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp182
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h10
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp11
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd2
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd2
-rw-r--r--modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd3
-rw-r--r--modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd3
-rw-r--r--modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd3
-rw-r--r--modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd3
-rw-r--r--modules/gdscript/editor/script_templates/RichTextEffect/default.gd7
-rw-r--r--modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd9
-rw-r--r--modules/gdscript/gdscript.cpp219
-rw-r--r--modules/gdscript/gdscript.h38
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp652
-rw-r--r--modules/gdscript/gdscript_analyzer.h4
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp28
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h5
-rw-r--r--modules/gdscript/gdscript_cache.cpp2
-rw-r--r--modules/gdscript/gdscript_codegen.h4
-rw-r--r--modules/gdscript/gdscript_compiler.cpp236
-rw-r--r--modules/gdscript/gdscript_compiler.h4
-rw-r--r--modules/gdscript/gdscript_editor.cpp39
-rw-r--r--modules/gdscript/gdscript_function.cpp40
-rw-r--r--modules/gdscript/gdscript_function.h190
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp78
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp424
-rw-r--r--modules/gdscript/gdscript_parser.h33
-rw-r--r--modules/gdscript/gdscript_rpc_callable.cpp7
-rw-r--r--modules/gdscript/gdscript_rpc_callable.h1
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp302
-rw-r--r--modules/gdscript/gdscript_tokenizer.h3
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp33
-rw-r--r--modules/gdscript/gdscript_vm.cpp24
-rw-r--r--modules/gdscript/gdscript_warning.cpp13
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/icons/GDScript.svg2
-rw-r--r--modules/gdscript/icons/GDScriptInternal.svg2
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp464
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h78
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp14
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp9
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h1
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp64
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h2
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp199
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h8
-rw-r--r--modules/gdscript/language_server/godot_lsp.h186
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd32
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd21
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd34
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd41
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd21
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd37
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out33
-rw-r--r--modules/gdscript/tests/scripts/lsp/class.notest.gd132
-rw-r--r--modules/gdscript/tests/scripts/lsp/enums.notest.gd26
-rw-r--r--modules/gdscript/tests/scripts/lsp/indentation.notest.gd28
-rw-r--r--modules/gdscript/tests/scripts/lsp/lambdas.notest.gd91
-rw-r--r--modules/gdscript/tests/scripts/lsp/local_variables.notest.gd25
-rw-r--r--modules/gdscript/tests/scripts/lsp/properties.notest.gd65
-rw-r--r--modules/gdscript/tests/scripts/lsp/scopes.notest.gd106
-rw-r--r--modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd56
-rw-r--r--modules/gdscript/tests/scripts/parser/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd20
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.out22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/super.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd40
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out29
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd71
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd72
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.out45
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd45
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.gd36
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.out13
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/object_constructor.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/object_constructor.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.gd3
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd281
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp9
-rw-r--r--modules/gdscript/tests/test_lsp.h480
-rw-r--r--modules/glslang/SCsub5
-rw-r--r--modules/glslang/glslang_resource_limits.h156
-rw-r--r--modules/glslang/register_types.cpp35
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml31
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml70
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml5
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml8
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp5
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp50
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h14
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp (renamed from modules/denoise/lightmap_denoiser.cpp)55
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_ktx.h (renamed from modules/denoise/lightmap_denoiser.h)33
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_webp.cpp45
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_webp.h6
-rw-r--r--modules/gltf/extensions/gltf_light.cpp2
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp32
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.h8
-rw-r--r--modules/gltf/gltf_document.cpp501
-rw-r--r--modules/gltf/gltf_document.h52
-rw-r--r--modules/gltf/gltf_state.cpp22
-rw-r--r--modules/gltf/gltf_state.h9
-rw-r--r--modules/gltf/register_types.cpp19
-rw-r--r--modules/gltf/structures/gltf_camera.cpp2
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml5
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp129
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h3
-rw-r--r--modules/gridmap/grid_map.cpp8
-rw-r--r--modules/ktx/SCsub58
-rw-r--r--modules/ktx/config.py7
-rw-r--r--modules/ktx/register_types.cpp (renamed from modules/denoise/register_types.cpp)14
-rw-r--r--modules/ktx/register_types.h (renamed from modules/denoise/register_types.h)10
-rw-r--r--modules/ktx/texture_loader_ktx.cpp562
-rw-r--r--modules/ktx/texture_loader_ktx.h (renamed from modules/dds/image_loader_dds.h)21
-rw-r--r--modules/lightmapper_rd/config.py2
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp164
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h13
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl167
-rw-r--r--modules/lightmapper_rd/register_types.cpp1
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp18
-rw-r--r--modules/mbedtls/packet_peer_mbed_dtls.cpp4
-rw-r--r--modules/mbedtls/stream_peer_mbedtls.cpp4
-rw-r--r--modules/minimp3/SCsub3
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp1
-rw-r--r--modules/minimp3/config.py8
-rw-r--r--modules/minimp3/resource_importer_mp3.cpp4
-rw-r--r--modules/mono/build_scripts/mono_configure.py4
-rw-r--r--modules/mono/class_db_api_json.cpp2
-rw-r--r--modules/mono/config.py4
-rw-r--r--modules/mono/csharp_script.cpp101
-rw-r--r--modules/mono/csharp_script.h31
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs13
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs34
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs44
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs23
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs468
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs40
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs694
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs293
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs50
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs93
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs13
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs19
-rw-r--r--modules/mono/editor/bindings_generator.cpp402
-rw-r--r--modules/mono/editor/bindings_generator.h34
-rw-r--r--modules/mono/editor/code_completion.cpp1
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp24
-rw-r--r--modules/mono/editor/hostfxr_resolver.cpp9
-rw-r--r--modules/mono/glue/GodotSharp/.editorconfig11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Compat.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs74
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs71
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs162
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs106
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj3
-rw-r--r--modules/mono/glue/runtime_interop.cpp26
-rw-r--r--modules/mono/godotsharp_dirs.cpp47
-rw-r--r--modules/mono/icons/BuildCSharp.svg1
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp6
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp3
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h4
-rw-r--r--modules/multiplayer/doc_classes/SceneReplicationConfig.xml38
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp23
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp141
-rw-r--r--modules/multiplayer/editor/replication_editor.h6
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp4
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp6
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp10
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp14
-rw-r--r--modules/multiplayer/scene_cache_interface.h1
-rw-r--r--modules/multiplayer/scene_replication_config.cpp167
-rw-r--r--modules/multiplayer/scene_replication_config.h24
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp28
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp6
-rw-r--r--modules/navigation/config.py2
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp31
-rw-r--r--modules/navigation/godot_navigation_server.cpp269
-rw-r--r--modules/navigation/godot_navigation_server.h15
-rw-r--r--modules/navigation/godot_navigation_server_2d.cpp369
-rw-r--r--modules/navigation/godot_navigation_server_2d.h234
-rw-r--r--modules/navigation/nav_map.cpp14
-rw-r--r--modules/navigation/nav_mesh_generator_2d.cpp830
-rw-r--r--modules/navigation/nav_mesh_generator_2d.h100
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp838
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h109
-rw-r--r--modules/navigation/nav_region.cpp4
-rw-r--r--modules/navigation/navigation_mesh_generator.cpp720
-rw-r--r--modules/navigation/navigation_mesh_generator.h11
-rw-r--r--modules/navigation/register_types.cpp15
-rw-r--r--modules/noise/doc_classes/FastNoiseLite.xml4
-rw-r--r--modules/noise/doc_classes/Noise.xml2
-rw-r--r--modules/noise/doc_classes/NoiseTexture2D.xml4
-rw-r--r--modules/noise/doc_classes/NoiseTexture3D.xml4
-rw-r--r--modules/noise/fastnoise_lite.h2
-rw-r--r--modules/noise/icons/NoiseTexture3D.svg1
-rw-r--r--modules/noise/noise_texture_3d.cpp4
-rw-r--r--modules/noise/noise_texture_3d.h2
-rw-r--r--modules/openxr/SCsub7
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp11
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.cpp5
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.h2
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile_metadata.cpp (renamed from modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp)63
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile_metadata.h (renamed from modules/openxr/action_map/openxr_interaction_profile_meta_data.h)27
-rw-r--r--modules/openxr/config.py3
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml114
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml167
-rw-r--r--modules/openxr/doc_classes/OpenXRInteractionProfile.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRInteractionProfileMetadata.xml54
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml166
-rw-r--r--modules/openxr/editor/openxr_action_editor.cpp4
-rw-r--r--modules/openxr/editor/openxr_action_set_editor.cpp9
-rw-r--r--modules/openxr/editor/openxr_interaction_profile_editor.cpp15
-rw-r--r--modules/openxr/editor/openxr_interaction_profile_editor.h6
-rw-r--r--modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp4
-rw-r--r--modules/openxr/editor/openxr_select_interaction_profile_dialog.h2
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_fb_foveation_extension.cpp168
-rw-r--r--modules/openxr/extensions/openxr_fb_foveation_extension.h96
-rw-r--r--modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp2
-rw-r--r--modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp102
-rw-r--r--modules/openxr/extensions/openxr_fb_update_swapchain_extension.h73
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp59
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h8
-rw-r--r--modules/openxr/extensions/openxr_htc_controller_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_huawei_controller_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_ml2_controller_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_opengl_extension.h35
-rw-r--r--modules/openxr/extensions/openxr_pico_controller_extension.cpp142
-rw-r--r--modules/openxr/extensions/openxr_vulkan_extension.h19
-rw-r--r--modules/openxr/extensions/openxr_wmr_controller_extension.cpp4
-rw-r--r--modules/openxr/openxr_api.cpp238
-rw-r--r--modules/openxr/openxr_api.h19
-rw-r--r--modules/openxr/openxr_interface.cpp217
-rw-r--r--modules/openxr/openxr_interface.h66
-rw-r--r--modules/openxr/openxr_platform_inc.h (renamed from modules/denoise/denoise_wrapper.cpp)72
-rw-r--r--modules/openxr/register_types.cpp24
-rw-r--r--modules/openxr/scene/openxr_hand.cpp4
-rw-r--r--modules/openxr/scene/openxr_hand.h4
-rw-r--r--modules/raycast/raycast_occlusion_cull.cpp80
-rw-r--r--modules/raycast/raycast_occlusion_cull.h4
-rw-r--r--modules/regex/doc_classes/RegEx.xml2
-rw-r--r--modules/regex/icons/RegEx.svg1
-rw-r--r--modules/regex/icons/RegExMatch.svg1
-rw-r--r--modules/regex/tests/test_regex.h80
-rw-r--r--modules/svg/SCsub84
-rw-r--r--modules/svg/image_loader_svg.cpp2
-rw-r--r--modules/text_server_adv/SCsub10
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct92
-rw-r--r--modules/text_server_adv/text_server_adv.cpp486
-rw-r--r--modules/text_server_adv/text_server_adv.h22
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.cpp21
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.h14
-rw-r--r--modules/text_server_fb/SCsub6
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct92
-rw-r--r--modules/text_server_fb/text_server_fb.cpp358
-rw-r--r--modules/text_server_fb/text_server_fb.h17
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.cpp6
-rw-r--r--modules/upnp/upnp.cpp4
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp4
-rw-r--r--modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml2
-rw-r--r--modules/webrtc/doc_classes/WebRTCPeerConnection.xml2
-rw-r--r--modules/webrtc/register_types.cpp8
-rw-r--r--modules/webrtc/webrtc_data_channel.cpp2
-rw-r--r--modules/webrtc/webrtc_data_channel.h2
-rw-r--r--modules/webrtc/webrtc_data_channel_extension.h1
-rw-r--r--modules/webrtc/webrtc_peer_connection_extension.h1
-rw-r--r--modules/websocket/SCsub2
-rw-r--r--modules/websocket/wsl_peer.cpp4
-rw-r--r--modules/zip/doc_classes/ZIPReader.xml9
-rw-r--r--modules/zip/zip_packer.cpp6
-rw-r--r--modules/zip/zip_packer.h6
-rw-r--r--modules/zip/zip_reader.cpp20
-rw-r--r--modules/zip/zip_reader.h5
420 files changed, 14913 insertions, 5688 deletions
diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp
index 86f3385ae3..7c0bc4ac82 100644
--- a/modules/basis_universal/register_types.cpp
+++ b/modules/basis_universal/register_types.cpp
@@ -162,7 +162,7 @@ static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size
const uint8_t *ptr = p_data;
int size = p_size;
- ERR_FAIL_COND_V_MSG(p_data == nullptr, image, "Cannot unpack invalid basis universal data.");
+ ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid basis universal data.");
basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats;
Image::Format imgfmt = Image::FORMAT_MAX;
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 4c217dac28..0656f8224c 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -300,7 +300,7 @@ void CSGShape3D::_update_shape() {
root_mesh.unref(); //byebye root mesh
CSGBrush *n = _get_brush();
- ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush.");
+ ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
OAHashMap<Vector3, Vector3> vec_map;
@@ -458,7 +458,7 @@ void CSGShape3D::_update_shape() {
void CSGShape3D::_update_collision_faces() {
if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
CSGBrush *n = _get_brush();
- ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush.");
+ ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
Vector<Vector3> physics_faces;
physics_faces.resize(n->faces.size() * 3);
Vector3 *physicsw = physics_faces.ptrw();
@@ -1933,7 +1933,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
case PATH_ROTATION_PATH:
break;
case PATH_ROTATION_PATH_FOLLOW:
- current_up = curve->sample_baked_up_vector(0);
+ current_up = curve->sample_baked_up_vector(0, true);
break;
}
@@ -2020,7 +2020,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
case PATH_ROTATION_PATH:
break;
case PATH_ROTATION_PATH_FOLLOW:
- current_up = curve->sample_baked_up_vector(current_offset);
+ current_up = curve->sample_baked_up_vector(current_offset, true);
break;
}
diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml
index 9a0f121e19..96d89ff486 100644
--- a/modules/csg/doc_classes/CSGMesh3D.xml
+++ b/modules/csg/doc_classes/CSGMesh3D.xml
@@ -16,7 +16,8 @@
</member>
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
The [Mesh] resource to use as a CSG shape.
- [b]Note:[/b] When using an [ArrayMesh], avoid meshes with vertex normals unless a flat shader is required. By default, CSGMesh will ignore the mesh's vertex normals and use a smooth shader calculated using the faces' normals. If a flat shader is required, ensure that all faces' vertex normals are parallel.
+ [b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU.
+ [constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal.
</member>
</members>
</class>
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 2c533cb36d..ebf0f5a91f 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -35,12 +35,15 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
///////////
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
+ helper.instantiate();
+
Color gizmo_color = EDITOR_DEF("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);
@@ -56,6 +59,9 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
create_handle_material("handles");
}
+CSGShape3DGizmoPlugin::~CSGShape3DGizmoPlugin() {
+}
+
String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
@@ -64,7 +70,7 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo,
}
if (Object::cast_to<CSGBox3D>(cs)) {
- return "Size";
+ return helper->box_get_handle_name(p_id);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -104,17 +110,15 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo
return Variant();
}
+void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+ helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
+}
+
void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
- Transform3D gt = cs->get_global_transform();
- //gt.orthonormalize();
- Transform3D gi = gt.affine_inverse();
-
- Vector3 ray_from = p_camera->project_ray_origin(p_point);
- Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
- Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+ Vector3 sg[2];
+ helper->get_segment(p_camera, p_point, sg);
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
@@ -135,29 +139,11 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
if (Object::cast_to<CSGBox3D>(cs)) {
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
-
- Vector3 axis;
- axis[p_id] = 1.0;
- Vector3 ra, rb;
- Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
- float d = ra[p_id];
-
- if (Math::is_nan(d)) {
- // The handle is perpendicular to the camera.
- return;
- }
-
- if (Node3DEditor::get_singleton()->is_snap_enabled()) {
- d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
- }
-
- if (d < 0.001) {
- d = 0.001;
- }
-
- Vector3 h = s->get_size();
- h[p_id] = d * 2;
- s->set_size(h);
+ Vector3 size = s->get_size();
+ Vector3 position;
+ helper->box_set_handle(sg, p_id, size, position);
+ s->set_size(size);
+ s->set_global_position(position);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -225,17 +211,7 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
}
if (Object::cast_to<CSGBox3D>(cs)) {
- CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
- if (p_cancel) {
- s->set_size(p_restore);
- return;
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Change Box Shape Size"));
- ur->add_do_method(s, "set_size", s->get_size());
- ur->add_undo_method(s, "set_size", p_restore);
- ur->commit_action();
+ helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -394,15 +370,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<CSGBox3D>(cs)) {
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
-
- Vector<Vector3> handles;
-
- for (int i = 0; i < 3; i++) {
- Vector3 h;
- h[i] = s->get_size()[i] / 2;
- handles.push_back(h);
- }
-
+ Vector<Vector3> handles = helper->box_get_handles(s->get_size());
p_gizmo->add_handles(handles, handles_material);
}
diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h
index deac1d428d..6281db0a21 100644
--- a/modules/csg/editor/csg_gizmos.h
+++ b/modules/csg/editor/csg_gizmos.h
@@ -38,9 +38,13 @@
#include "editor/editor_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
+class Gizmo3DHelper;
+
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
+ Ref<Gizmo3DHelper> helper;
+
public:
virtual bool has_gizmo(Node3D *p_spatial) override;
virtual String get_gizmo_name() const override;
@@ -50,10 +54,12 @@ public:
virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+ void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override;
CSGShape3DGizmoPlugin();
+ ~CSGShape3DGizmoPlugin();
};
class EditorPluginCSG : public EditorPlugin {
diff --git a/modules/csg/icons/CSGTorus3D.svg b/modules/csg/icons/CSGTorus3D.svg
index 5672244e5c..27a6b422f9 100644
--- a/modules/csg/icons/CSGTorus3D.svg
+++ b/modules/csg/icons/CSGTorus3D.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><ellipse cx="8" cy="7.5" fill="none" rx="6" ry="3.5" stroke="#fc7f7f" stroke-width="2" mask="url(#a)"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z" stroke-width="2"/><path d="M6.2 7.2a2 1 0 1 0 3.6 0" stroke-width="1.75" stroke-linecap="round"/></g></svg>
diff --git a/modules/dds/image_loader_dds.cpp b/modules/dds/image_loader_dds.cpp
deleted file mode 100644
index 42c8120595..0000000000
--- a/modules/dds/image_loader_dds.cpp
+++ /dev/null
@@ -1,422 +0,0 @@
-/**************************************************************************/
-/* image_loader_dds.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 "image_loader_dds.h"
-
-#include "core/os/os.h"
-
-#include "core/io/file_access.h"
-#include "core/io/file_access_memory.h"
-
-#include <string.h>
-
-#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])))
-
-// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
-
-enum {
- DDS_MAGIC = 0x20534444,
- DDSD_PITCH = 0x00000008,
- DDSD_LINEARSIZE = 0x00080000,
- DDSD_MIPMAPCOUNT = 0x00020000,
- DDPF_FOURCC = 0x00000004,
- DDPF_ALPHAPIXELS = 0x00000001,
- DDPF_INDEXED = 0x00000020,
- DDPF_RGB = 0x00000040,
-};
-
-enum DDSFormat {
- DDS_DXT1,
- DDS_DXT3,
- DDS_DXT5,
- DDS_ATI1,
- DDS_ATI2,
- DDS_A2XY,
- DDS_BGRA8,
- DDS_BGR8,
- DDS_RGBA8, //flipped in dds
- DDS_RGB8, //flipped in dds
- DDS_BGR5A1,
- DDS_BGR565,
- DDS_BGR10A2,
- DDS_INDEXED,
- DDS_LUMINANCE,
- DDS_LUMINANCE_ALPHA,
- DDS_MAX
-};
-
-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 }
-};
-
-static Ref<Image> _dds_mem_loader_func(const uint8_t *p_buffer, int p_buffer_len) {
- Ref<FileAccessMemory> memfile;
- memfile.instantiate();
- Error open_memfile_error = memfile->open_custom(p_buffer, p_buffer_len);
- ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for DDS image buffer.");
-
- Ref<Image> img;
- img.instantiate();
- Error load_error = ImageLoaderDDS().load_image(img, memfile, false, 1.0f);
- ERR_FAIL_COND_V_MSG(load_error, Ref<Image>(), "Failed to load DDS image.");
- return img;
-}
-
-Error ImageLoaderDDS::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
- uint32_t magic = f->get_32();
- uint32_t hsize = f->get_32();
- uint32_t flags = f->get_32();
- uint32_t height = f->get_32();
- uint32_t width = f->get_32();
- uint32_t pitch = f->get_32();
- /* uint32_t depth = */ f->get_32();
- uint32_t mipmaps = f->get_32();
-
- //skip 11
- for (int i = 0; i < 11; i++) {
- f->get_32();
- }
-
- //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)...
- if (magic != DDS_MAGIC || hsize != 124) {
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid or unsupported DDS texture file '" + f->get_path() + "'.");
- }
-
- /* uint32_t format_size = */ f->get_32();
- uint32_t format_flags = f->get_32();
- uint32_t format_fourcc = f->get_32();
- uint32_t format_rgb_bits = f->get_32();
- uint32_t format_red_mask = f->get_32();
- uint32_t format_green_mask = f->get_32();
- uint32_t format_blue_mask = f->get_32();
- uint32_t format_alpha_mask = f->get_32();
-
- /* uint32_t caps_1 = */ f->get_32();
- /* uint32_t caps_2 = */ f->get_32();
- /* uint32_t caps_ddsx = */ f->get_32();
-
- //reserved skip
- f->get_32();
- f->get_32();
-
- /*
- print_line("DDS width: "+itos(width));
- print_line("DDS height: "+itos(height));
- print_line("DDS mipmaps: "+itos(mipmaps));
-
- 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);
- */
-
- //must avoid this later
- while (f->get_position() < 128) {
- f->get_8();
- }
-
- 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 && format_green_mask == 0xff && format_blue_mask == 0xff) {
- dds_format = DDS_LUMINANCE;
- } else if ((format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0xff && format_green_mask == 0xff && format_blue_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);
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Unrecognized or unsupported color layout in DDS '" + f->get_path() + "'.");
- }
-
- if (!(flags & DDSD_MIPMAPCOUNT)) {
- mipmaps = 1;
- }
-
- Vector<uint8_t> src_data;
-
- const DDSFormatInfo &info = dds_format_info[dds_format];
- uint32_t w = width;
- uint32_t h = height;
-
- if (info.compressed) {
- //compressed bc
-
- uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- ERR_FAIL_COND_V(size != pitch, ERR_FILE_CORRUPT);
- ERR_FAIL_COND_V(!(flags & DDSD_LINEARSIZE), ERR_FILE_CORRUPT);
-
- 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;
- }
-
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- } else if (info.palette) {
- //indexed
- ERR_FAIL_COND_V(!(flags & DDSD_PITCH), ERR_FILE_CORRUPT);
- ERR_FAIL_COND_V(format_rgb_bits != 8, ERR_FILE_CORRUPT);
-
- uint32_t size = pitch * height;
- ERR_FAIL_COND_V(size != width * height * info.block_size, ERR_FILE_CORRUPT);
-
- 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...
-
- uint32_t size = width * height * info.block_size;
-
- for (uint32_t i = 1; i < mipmaps; i++) {
- w = (w + 1) >> 1;
- h = (h + 1) >> 1;
- size += w * h * info.block_size;
- }
-
- if (dds_format == DDS_BGR565) {
- size = size * 3 / 2;
- } else if (dds_format == DDS_BGR5A1) {
- size = size * 2;
- }
-
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- switch (dds_format) {
- case DDS_BGR5A1: {
- // TO RGBA
- int colcount = size / 4;
-
- for (int i = colcount - 1; i >= 0; i--) {
- int src_ofs = i * 2;
- int dst_ofs = i * 4;
-
- uint8_t a = wb[src_ofs + 1] & 0x80;
- 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: {
- int colcount = size / 3;
-
- for (int i = colcount - 1; i >= 0; i--) {
- int src_ofs = i * 2;
- int dst_ofs = i * 3;
-
- 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;
- }
-
- } break;
- case DDS_BGR10A2: {
- // TO RGBA
- int colcount = size / 4;
-
- for (int i = colcount - 1; i >= 0; 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);
-
- uint8_t a = (w32 & 0xc0000000) >> 24;
- uint8_t r = (w32 & 0x3ff00000) >> 22;
- uint8_t g = (w32 & 0xffc00) >> 12;
- uint8_t b = (w32 & 0x3ff) >> 2;
-
- 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_BGRA8: {
- int colcount = size / 4;
-
- for (int i = 0; i < colcount; i++) {
- SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
- }
-
- } break;
- case DDS_BGR8: {
- 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;
-
- default: {
- }
- }
- }
-
- p_image->set_data(width, height, mipmaps - 1, info.format, src_data);
- return OK;
-}
-
-ImageLoaderDDS::ImageLoaderDDS() {
- Image::_dds_mem_loader_func = _dds_mem_loader_func;
-}
-
-void ImageLoaderDDS::get_recognized_extensions(List<String> *p_extensions) const {
- p_extensions->push_back("dds");
-}
diff --git a/modules/dds/register_types.cpp b/modules/dds/register_types.cpp
index b4d406eda9..d336269eb3 100644
--- a/modules/dds/register_types.cpp
+++ b/modules/dds/register_types.cpp
@@ -30,11 +30,9 @@
#include "register_types.h"
-#include "image_loader_dds.h"
#include "texture_loader_dds.h"
static Ref<ResourceFormatDDS> resource_loader_dds;
-static Ref<ImageLoaderDDS> image_loader_dds;
void initialize_dds_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
@@ -43,9 +41,6 @@ void initialize_dds_module(ModuleInitializationLevel p_level) {
resource_loader_dds.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_dds);
-
- image_loader_dds.instantiate();
- ImageLoader::add_image_format_loader(image_loader_dds);
}
void uninitialize_dds_module(ModuleInitializationLevel p_level) {
@@ -55,7 +50,4 @@ void uninitialize_dds_module(ModuleInitializationLevel p_level) {
ResourceLoader::remove_resource_format_loader(resource_loader_dds);
resource_loader_dds.unref();
-
- ImageLoader::remove_image_format_loader(image_loader_dds);
- image_loader_dds.unref();
}
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index 861cf20cc2..481bb46c24 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -29,11 +29,71 @@
/**************************************************************************/
#include "texture_loader_dds.h"
-#include "image_loader_dds.h"
#include "core/io/file_access.h"
#include "scene/resources/image_texture.h"
+#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])))
+
+// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
+
+enum {
+ DDS_MAGIC = 0x20534444,
+ DDSD_PITCH = 0x00000008,
+ DDSD_LINEARSIZE = 0x00080000,
+ DDSD_MIPMAPCOUNT = 0x00020000,
+ DDPF_FOURCC = 0x00000004,
+ DDPF_ALPHAPIXELS = 0x00000001,
+ DDPF_INDEXED = 0x00000020,
+ DDPF_RGB = 0x00000040,
+};
+
+enum DDSFormat {
+ DDS_DXT1,
+ DDS_DXT3,
+ DDS_DXT5,
+ DDS_ATI1,
+ DDS_ATI2,
+ DDS_A2XY,
+ DDS_BGRA8,
+ DDS_BGR8,
+ DDS_RGBA8, //flipped in dds
+ DDS_RGB8, //flipped in dds
+ DDS_BGR5A1,
+ DDS_BGR565,
+ DDS_BGR10A2,
+ DDS_LUMINANCE,
+ DDS_LUMINANCE_ALPHA,
+ DDS_MAX
+};
+
+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 }
+};
+
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;
@@ -52,12 +112,303 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open DDS texture file '" + p_path + "'.");
- Ref<Image> img = memnew(Image);
- Error i_error = ImageLoaderDDS().load_image(img, f, false, 1.0);
- if (r_error) {
- *r_error = i_error;
+ uint32_t magic = f->get_32();
+ uint32_t hsize = f->get_32();
+ uint32_t flags = f->get_32();
+ uint32_t height = f->get_32();
+ uint32_t width = f->get_32();
+ uint32_t pitch = f->get_32();
+ /* uint32_t depth = */ f->get_32();
+ uint32_t mipmaps = f->get_32();
+
+ //skip 11
+ for (int i = 0; i < 11; i++) {
+ f->get_32();
+ }
+
+ //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)...
+ if (magic != DDS_MAGIC || hsize != 124) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
+ }
+
+ /* uint32_t format_size = */ f->get_32();
+ uint32_t format_flags = f->get_32();
+ uint32_t format_fourcc = f->get_32();
+ uint32_t format_rgb_bits = f->get_32();
+ uint32_t format_red_mask = f->get_32();
+ uint32_t format_green_mask = f->get_32();
+ uint32_t format_blue_mask = f->get_32();
+ uint32_t format_alpha_mask = f->get_32();
+
+ /* uint32_t caps_1 = */ f->get_32();
+ /* uint32_t caps_2 = */ f->get_32();
+ /* uint32_t caps_ddsx = */ f->get_32();
+
+ //reserved skip
+ f->get_32();
+ f->get_32();
+
+ /*
+ print_line("DDS width: "+itos(width));
+ print_line("DDS height: "+itos(height));
+ print_line("DDS mipmaps: "+itos(mipmaps));
+
+ 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);
+ */
+
+ //must avoid this later
+ while (f->get_position() < 128) {
+ f->get_8();
+ }
+
+ 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);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
+ }
+
+ if (!(flags & DDSD_MIPMAPCOUNT)) {
+ mipmaps = 1;
+ }
+
+ Vector<uint8_t> src_data;
+
+ const DDSFormatInfo &info = dds_format_info[dds_format];
+ uint32_t w = width;
+ uint32_t h = height;
+
+ if (info.compressed) {
+ //compressed bc
+
+ 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>());
+
+ 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;
+ }
+
+ src_data.resize(size);
+ 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...
+
+ uint32_t size = width * height * info.block_size;
+
+ for (uint32_t i = 1; i < mipmaps; i++) {
+ w = (w + 1) >> 1;
+ h = (h + 1) >> 1;
+ size += w * h * info.block_size;
+ }
+
+ if (dds_format == DDS_BGR565) {
+ size = size * 3 / 2;
+ } else if (dds_format == DDS_BGR5A1) {
+ size = size * 2;
+ }
+
+ src_data.resize(size);
+ uint8_t *wb = src_data.ptrw();
+ f->get_buffer(wb, size);
+
+ switch (dds_format) {
+ case DDS_BGR5A1: {
+ // TO RGBA
+ int colcount = size / 4;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 4;
+
+ uint8_t a = wb[src_ofs + 1] & 0x80;
+ 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: {
+ int colcount = size / 3;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 3;
+
+ 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;
+ }
+
+ } break;
+ case DDS_BGR10A2: {
+ // TO RGBA
+ int colcount = size / 4;
+
+ for (int i = colcount - 1; i >= 0; 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);
+
+ uint8_t a = (w32 & 0xc0000000) >> 24;
+ uint8_t r = (w32 & 0x3ff00000) >> 22;
+ uint8_t g = (w32 & 0xffc00) >> 12;
+ uint8_t b = (w32 & 0x3ff) >> 2;
+
+ 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_BGRA8: {
+ int colcount = size / 4;
+
+ for (int i = 0; i < colcount; i++) {
+ SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
+ }
+
+ } break;
+ case DDS_BGR8: {
+ 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;
+
+ default: {
+ }
+ }
}
+ Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data));
Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
if (r_error) {
diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub
deleted file mode 100644
index 779ce165d2..0000000000
--- a/modules/denoise/SCsub
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env python
-
-import resource_to_cpp
-
-Import("env")
-Import("env_modules")
-
-env_oidn = env_modules.Clone()
-
-# Thirdparty source files
-
-thirdparty_obj = []
-
-thirdparty_dir = "#thirdparty/oidn/"
-thirdparty_sources = [
- "core/api.cpp",
- "core/device.cpp",
- "core/filter.cpp",
- "core/network.cpp",
- "core/autoencoder.cpp",
- "core/transfer_function.cpp",
- "weights/rtlightmap_hdr.gen.cpp",
- "mkl-dnn/src/common/batch_normalization.cpp",
- "mkl-dnn/src/common/concat.cpp",
- "mkl-dnn/src/common/convolution.cpp",
- "mkl-dnn/src/common/convolution_pd.cpp",
- "mkl-dnn/src/common/deconvolution.cpp",
- "mkl-dnn/src/common/eltwise.cpp",
- "mkl-dnn/src/common/engine.cpp",
- "mkl-dnn/src/common/inner_product.cpp",
- "mkl-dnn/src/common/inner_product_pd.cpp",
- "mkl-dnn/src/common/lrn.cpp",
- "mkl-dnn/src/common/memory.cpp",
- "mkl-dnn/src/common/memory_desc_wrapper.cpp",
- "mkl-dnn/src/common/mkldnn_debug.cpp",
- "mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp",
- "mkl-dnn/src/common/pooling.cpp",
- "mkl-dnn/src/common/primitive.cpp",
- "mkl-dnn/src/common/primitive_attr.cpp",
- "mkl-dnn/src/common/primitive_desc.cpp",
- "mkl-dnn/src/common/primitive_exec_types.cpp",
- "mkl-dnn/src/common/primitive_iterator.cpp",
- "mkl-dnn/src/common/query.cpp",
- "mkl-dnn/src/common/reorder.cpp",
- "mkl-dnn/src/common/rnn.cpp",
- "mkl-dnn/src/common/scratchpad.cpp",
- "mkl-dnn/src/common/shuffle.cpp",
- "mkl-dnn/src/common/softmax.cpp",
- "mkl-dnn/src/common/stream.cpp",
- "mkl-dnn/src/common/sum.cpp",
- "mkl-dnn/src/common/utils.cpp",
- "mkl-dnn/src/common/verbose.cpp",
- "mkl-dnn/src/cpu/cpu_barrier.cpp",
- "mkl-dnn/src/cpu/cpu_concat.cpp",
- "mkl-dnn/src/cpu/cpu_engine.cpp",
- "mkl-dnn/src/cpu/cpu_memory.cpp",
- "mkl-dnn/src/cpu/cpu_reducer.cpp",
- "mkl-dnn/src/cpu/cpu_reorder.cpp",
- "mkl-dnn/src/cpu/cpu_sum.cpp",
- "mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp",
- "mkl-dnn/src/cpu/jit_avx2_convolution.cpp",
- "mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp",
- "mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp",
- "mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp",
- "mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp",
- "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp",
- "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp",
- "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp",
- "mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp",
- "mkl-dnn/src/cpu/jit_sse42_convolution.cpp",
- "mkl-dnn/src/cpu/jit_transpose_src_utils.cpp",
- "mkl-dnn/src/cpu/jit_uni_eltwise.cpp",
- "mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp",
- "mkl-dnn/src/cpu/jit_uni_pooling.cpp",
- "mkl-dnn/src/cpu/jit_uni_reorder.cpp",
- "mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp",
- "mkl-dnn/src/cpu/jit_utils/jit_utils.cpp",
- "mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c",
- "common/platform.cpp",
- "common/thread.cpp",
- "common/tensor.cpp",
-]
-thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
-
-thirdparty_include_dirs = [
- "",
- "include",
- "mkl-dnn/include",
- "mkl-dnn/src",
- "mkl-dnn/src/common",
- "mkl-dnn/src/cpu/xbyak",
- "mkl-dnn/src/cpu",
-]
-thirdparty_include_dirs = [thirdparty_dir + file for file in thirdparty_include_dirs]
-
-
-env_oidn.Prepend(CPPPATH=thirdparty_include_dirs)
-env_oidn.Append(
- CPPDEFINES=[
- "MKLDNN_THR=MKLDNN_THR_SEQ",
- "OIDN_STATIC_LIB",
- "__STDC_CONSTANT_MACROS",
- "__STDC_LIMIT_MACROS",
- "DISABLE_VERBOSE",
- "MKLDNN_ENABLE_CONCURRENT_EXEC",
- ]
-)
-env_oidn.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds.
-
-env_thirdparty = env_oidn.Clone()
-env_thirdparty.disable_warnings()
-env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
-env.modules_sources += thirdparty_obj
-
-weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza"
-weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp"
-
-env_thirdparty.Depends(weights_out_path, weights_in_path)
-env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp)
-
-# Godot source files
-
-module_obj = []
-
-env_oidn.add_source_files(module_obj, "*.cpp")
-env.modules_sources += module_obj
-
-# Needed to force rebuilding the module files when the thirdparty library is updated.
-env.Depends(module_obj, thirdparty_obj)
diff --git a/modules/denoise/config.py b/modules/denoise/config.py
deleted file mode 100644
index 27d2ffbf86..0000000000
--- a/modules/denoise/config.py
+++ /dev/null
@@ -1,12 +0,0 @@
-def can_build(env, platform):
- # Thirdparty dependency OpenImage Denoise includes oneDNN library
- # and the version we use only supports x86_64.
- # It's also only relevant for tools build and desktop platforms,
- # as doing lightmap generation and denoising on Android or Web
- # would be a bit far-fetched.
- desktop_platforms = ["linuxbsd", "macos", "windows"]
- return env.editor_build and platform in desktop_platforms and env["arch"] == "x86_64"
-
-
-def configure(env):
- pass
diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h
deleted file mode 100644
index d4bf154a5d..0000000000
--- a/modules/denoise/denoise_wrapper.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/**************************************************************************/
-/* denoise_wrapper.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef DENOISE_WRAPPER_H
-#define DENOISE_WRAPPER_H
-
-void *oidn_denoiser_init();
-bool oidn_denoise(void *device, float *p_floats, int p_width, int p_height);
-void oidn_denoiser_finish(void *device);
-
-#endif // DENOISE_WRAPPER_H
diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py
deleted file mode 100644
index a89eda9117..0000000000
--- a/modules/denoise/resource_to_cpp.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-
-## ======================================================================== ##
-## Copyright 2009-2019 Intel Corporation ##
-## ##
-## Licensed under the Apache License, Version 2.0 (the "License"); ##
-## you may not use this file except in compliance with the License. ##
-## You may obtain a copy of the License at ##
-## ##
-## http://www.apache.org/licenses/LICENSE-2.0 ##
-## ##
-## Unless required by applicable law or agreed to in writing, software ##
-## distributed under the License is distributed on an "AS IS" BASIS, ##
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ##
-## See the License for the specific language governing permissions and ##
-## limitations under the License. ##
-## ======================================================================== ##
-
-import os
-from array import array
-
-
-# Generates a C++ file from the specified binary resource file
-def generate(in_path, out_path):
- namespace = "oidn::weights"
- scopes = namespace.split("::")
-
- file_name = os.path.basename(in_path)
- var_name = os.path.splitext(file_name)[0]
-
- with open(in_path, "rb") as in_file, open(out_path, "w") as out_file:
- # Header
- out_file.write("// Generated from: %s\n" % file_name)
- out_file.write("#include <cstddef>\n\n")
-
- # Open the namespaces
- for s in scopes:
- out_file.write("namespace %s {\n" % s)
- if scopes:
- out_file.write("\n")
-
- # Read the file
- in_data = array("B", in_file.read())
-
- # Write the size
- out_file.write("//const size_t %s_size = %d;\n\n" % (var_name, len(in_data)))
-
- # Write the data
- out_file.write("unsigned char %s[] = {" % var_name)
- for i in range(len(in_data)):
- c = in_data[i]
- if i > 0:
- out_file.write(",")
- if (i + 1) % 20 == 1:
- out_file.write("\n")
- out_file.write("%d" % c)
- out_file.write("\n};\n")
-
- # Close the namespaces
- if scopes:
- out_file.write("\n")
- for scope in reversed(scopes):
- out_file.write("} // namespace %s\n" % scope)
-
-
-def tza_to_cpp(target, source, env):
- for x in zip(source, target):
- generate(str(x[0]), str(x[1]))
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp
index 0ace89caa5..94473c76c0 100644
--- a/modules/enet/enet_connection.cpp
+++ b/modules/enet/enet_connection.cpp
@@ -37,7 +37,7 @@
#include "core/variant/typed_array.h"
void ENetConnection::broadcast(enet_uint8 p_channel, ENetPacket *p_packet) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_MSG(p_channel >= host->channelLimit, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)host->channelLimit));
enet_host_broadcast(host, p_channel, p_packet);
}
@@ -71,7 +71,7 @@ Error ENetConnection::create_host(int p_max_peers, int p_max_channels, int p_in_
}
void ENetConnection::destroy() {
- ERR_FAIL_COND_MSG(!host, "Host already destroyed");
+ ERR_FAIL_NULL_MSG(host, "Host already destroyed.");
for (List<Ref<ENetPacketPeer>>::Element *E = peers.front(); E; E = E->next()) {
E->get()->_on_disconnect();
}
@@ -82,7 +82,7 @@ void ENetConnection::destroy() {
Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int p_port, int p_channels, int p_data) {
Ref<ENetPacketPeer> out;
- ERR_FAIL_COND_V_MSG(!host, out, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, out, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_V_MSG(peers.size(), out, "The ENetConnection is already connected to a peer.");
ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, out, "The remote port number must be between 1 and 65535 (inclusive).");
@@ -142,7 +142,7 @@ ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event,
return EVENT_ERROR;
} break;
case ENET_EVENT_TYPE_RECEIVE: {
- // Packet reveived.
+ // Packet received.
if (p_event.peer->data != nullptr) {
Ref<ENetPacketPeer> pp = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
r_event.peer = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
@@ -160,7 +160,7 @@ ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event,
}
ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) {
- ERR_FAIL_COND_V_MSG(!host, EVENT_ERROR, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, EVENT_ERROR, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_V(r_event.peer.is_valid(), EVENT_ERROR);
// Drop peers that have already been disconnected.
@@ -186,7 +186,7 @@ ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event)
}
int ENetConnection::check_events(EventType &r_type, Event &r_event) {
- ERR_FAIL_COND_V_MSG(!host, -1, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, -1, "The ENetConnection instance isn't currently active.");
ENetEvent event;
int ret = enet_host_check_events(host, &event);
if (ret < 0) {
@@ -198,32 +198,32 @@ int ENetConnection::check_events(EventType &r_type, Event &r_event) {
}
void ENetConnection::flush() {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
enet_host_flush(host);
}
void ENetConnection::bandwidth_limit(int p_in_bandwidth, int p_out_bandwidth) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
enet_host_bandwidth_limit(host, p_in_bandwidth, p_out_bandwidth);
}
void ENetConnection::channel_limit(int p_max_channels) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
enet_host_channel_limit(host, p_max_channels);
}
void ENetConnection::bandwidth_throttle() {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
enet_host_bandwidth_throttle(host);
}
void ENetConnection::compress(CompressionMode p_mode) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
Compressor::setup(host, p_mode);
}
double ENetConnection::pop_statistic(HostStatistic p_stat) {
- ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
uint32_t *ptr = nullptr;
switch (p_stat) {
case HOST_TOTAL_SENT_DATA:
@@ -239,19 +239,19 @@ double ENetConnection::pop_statistic(HostStatistic p_stat) {
ptr = &(host->totalReceivedPackets);
break;
}
- ERR_FAIL_COND_V_MSG(ptr == nullptr, 0, "Invalid statistic: " + itos(p_stat));
+ ERR_FAIL_NULL_V_MSG(ptr, 0, "Invalid statistic: " + itos(p_stat) + ".");
uint32_t ret = *ptr;
*ptr = 0;
return ret;
}
int ENetConnection::get_max_channels() const {
- ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
return host->channelLimit;
}
int ENetConnection::get_local_port() const {
- ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound");
ENetAddress address;
ERR_FAIL_COND_V_MSG(enet_socket_get_address(host->socket, &address), 0, "Unable to get socket address");
@@ -265,7 +265,7 @@ void ENetConnection::get_peers(List<Ref<ENetPacketPeer>> &r_peers) {
}
TypedArray<ENetPacketPeer> ENetConnection::_get_peers() {
- ERR_FAIL_COND_V_MSG(!host, Array(), "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, Array(), "The ENetConnection instance isn't currently active.");
TypedArray<ENetPacketPeer> out;
for (const Ref<ENetPacketPeer> &I : peers) {
out.push_back(I);
@@ -275,7 +275,7 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() {
Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) {
#ifdef GODOT_ENET
- ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
#else
@@ -285,7 +285,7 @@ Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) {
void ENetConnection::refuse_new_connections(bool p_refuse) {
#ifdef GODOT_ENET
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
enet_host_refuse_new_connections(host, p_refuse);
#else
ERR_FAIL_MSG("ENet DTLS support not available in this build.");
@@ -294,7 +294,7 @@ void ENetConnection::refuse_new_connections(bool p_refuse) {
Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) {
#ifdef GODOT_ENET
- ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER);
return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
#else
@@ -315,7 +315,7 @@ Error ENetConnection::_create(ENetAddress *p_address, int p_max_peers, int p_max
p_in_bandwidth /* limit incoming bandwidth if > 0 */,
p_out_bandwidth /* limit outgoing bandwidth if > 0 */);
- ERR_FAIL_COND_V_MSG(!host, ERR_CANT_CREATE, "Couldn't create an ENet host.");
+ ERR_FAIL_NULL_V_MSG(host, ERR_CANT_CREATE, "Couldn't create an ENet host.");
return OK;
}
@@ -335,7 +335,7 @@ Array ENetConnection::_service(int p_timeout) {
}
void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_flags) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_MSG(p_channel < 0 || p_channel > (int)host->channelLimit, "Invalid channel");
ERR_FAIL_COND_MSG(p_flags & ~ENetPacketPeer::FLAG_ALLOWED, "Invalid flags");
ENetPacket *pkt = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags);
@@ -343,7 +343,7 @@ void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_f
}
void ENetConnection::socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet) {
- ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound");
ERR_FAIL_COND_MSG(p_port < 1 || p_port > 65535, "The remote port number must be between 1 and 65535 (inclusive).");
@@ -497,7 +497,7 @@ size_t ENetConnection::Compressor::enet_decompress(void *context, const enet_uin
}
void ENetConnection::Compressor::setup(ENetHost *p_host, CompressionMode p_mode) {
- ERR_FAIL_COND(!p_host);
+ ERR_FAIL_NULL(p_host);
switch (p_mode) {
case COMPRESS_NONE: {
enet_host_compress(p_host, nullptr);
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index af5bd2929c..63f12ea1c1 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -343,23 +343,22 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
int packet_flags = 0;
int channel = SYSCH_RELIABLE;
int tr_channel = get_transfer_channel();
+ switch (get_transfer_mode()) {
+ case TRANSFER_MODE_UNRELIABLE: {
+ packet_flags = ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
+ channel = SYSCH_UNRELIABLE;
+ } break;
+ case TRANSFER_MODE_UNRELIABLE_ORDERED: {
+ packet_flags = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
+ channel = SYSCH_UNRELIABLE;
+ } break;
+ case TRANSFER_MODE_RELIABLE: {
+ packet_flags = ENET_PACKET_FLAG_RELIABLE;
+ channel = SYSCH_RELIABLE;
+ } break;
+ }
if (tr_channel > 0) {
channel = SYSCH_MAX + tr_channel - 1;
- } else {
- switch (get_transfer_mode()) {
- case TRANSFER_MODE_UNRELIABLE: {
- packet_flags = ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
- channel = SYSCH_UNRELIABLE;
- } break;
- case TRANSFER_MODE_UNRELIABLE_ORDERED: {
- packet_flags = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
- channel = SYSCH_UNRELIABLE;
- } break;
- case TRANSFER_MODE_RELIABLE: {
- packet_flags = ENET_PACKET_FLAG_RELIABLE;
- channel = SYSCH_RELIABLE;
- } break;
- }
}
#ifdef DEBUG_ENABLED
diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp
index a131841a07..f2bf5337ee 100644
--- a/modules/enet/enet_packet_peer.cpp
+++ b/modules/enet/enet_packet_peer.cpp
@@ -31,51 +31,51 @@
#include "enet_packet_peer.h"
void ENetPacketPeer::peer_disconnect(int p_data) {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
enet_peer_disconnect(peer, p_data);
}
void ENetPacketPeer::peer_disconnect_later(int p_data) {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
enet_peer_disconnect_later(peer, p_data);
}
void ENetPacketPeer::peer_disconnect_now(int p_data) {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
enet_peer_disconnect_now(peer, p_data);
_on_disconnect();
}
void ENetPacketPeer::ping() {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
enet_peer_ping(peer);
}
void ENetPacketPeer::ping_interval(int p_interval) {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
enet_peer_ping_interval(peer, p_interval);
}
int ENetPacketPeer::send(uint8_t p_channel, ENetPacket *p_packet) {
- ERR_FAIL_COND_V(peer == nullptr, -1);
- ERR_FAIL_COND_V(p_packet == nullptr, -1);
+ ERR_FAIL_NULL_V(peer, -1);
+ ERR_FAIL_NULL_V(p_packet, -1);
ERR_FAIL_COND_V_MSG(p_channel >= peer->channelCount, -1, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)peer->channelCount));
return enet_peer_send(peer, p_channel, p_packet);
}
void ENetPacketPeer::reset() {
- ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected");
+ ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
enet_peer_reset(peer);
_on_disconnect();
}
void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int p_deceleration) {
- ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected");
+ ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
enet_peer_throttle_configure(peer, p_interval, p_acceleration, p_deceleration);
}
void ENetPacketPeer::set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max) {
- ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected");
+ ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less than maximum timeout");
enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max);
}
@@ -89,7 +89,7 @@ int ENetPacketPeer::get_available_packet_count() const {
}
Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
- ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!packet_queue.size(), ERR_UNAVAILABLE);
if (last_packet) {
enet_packet_destroy(last_packet);
@@ -103,13 +103,13 @@ Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
}
Error ENetPacketPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
- ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
ENetPacket *packet = enet_packet_create(p_buffer, p_buffer_size, ENET_PACKET_FLAG_RELIABLE);
return send(0, packet) < 0 ? FAILED : OK;
}
IPAddress ENetPacketPeer::get_remote_address() const {
- ERR_FAIL_COND_V(!peer, IPAddress());
+ ERR_FAIL_NULL_V(peer, IPAddress());
IPAddress out;
#ifdef GODOT_ENET
out.set_ipv6((uint8_t *)&(peer->address.host));
@@ -120,7 +120,7 @@ IPAddress ENetPacketPeer::get_remote_address() const {
}
int ENetPacketPeer::get_remote_port() const {
- ERR_FAIL_COND_V(!peer, 0);
+ ERR_FAIL_NULL_V(peer, 0);
return peer->address.port;
}
@@ -129,7 +129,7 @@ bool ENetPacketPeer::is_active() const {
}
double ENetPacketPeer::get_statistic(PeerStatistic p_stat) {
- ERR_FAIL_COND_V(!peer, 0);
+ ERR_FAIL_NULL_V(peer, 0);
switch (p_stat) {
case PEER_PACKET_LOSS:
return peer->packetLoss;
@@ -171,7 +171,7 @@ ENetPacketPeer::PeerState ENetPacketPeer::get_state() const {
}
int ENetPacketPeer::get_channels() const {
- ERR_FAIL_COND_V_MSG(!peer, 0, "The ENetConnection instance isn't currently active.");
+ ERR_FAIL_NULL_V_MSG(peer, 0, "The ENetConnection instance isn't currently active.");
return peer->channelCount;
}
@@ -183,12 +183,12 @@ void ENetPacketPeer::_on_disconnect() {
}
void ENetPacketPeer::_queue_packet(ENetPacket *p_packet) {
- ERR_FAIL_COND(!peer);
+ ERR_FAIL_NULL(peer);
packet_queue.push_back(p_packet);
}
Error ENetPacketPeer::_send(int p_channel, PackedByteArray p_packet, int p_flags) {
- ERR_FAIL_COND_V_MSG(peer == nullptr, ERR_UNCONFIGURED, "Peer not connected");
+ ERR_FAIL_NULL_V_MSG(peer, ERR_UNCONFIGURED, "Peer not connected.");
ERR_FAIL_COND_V_MSG(p_channel < 0 || p_channel > (int)peer->channelCount, ERR_INVALID_PARAMETER, "Invalid channel");
ERR_FAIL_COND_V_MSG(p_flags & ~FLAG_ALLOWED, ERR_INVALID_PARAMETER, "Invalid flags");
ENetPacket *packet = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags);
diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub
index 421f200f1a..2813eaecd5 100644
--- a/modules/freetype/SCsub
+++ b/modules/freetype/SCsub
@@ -61,12 +61,6 @@ if env["builtin_freetype"]:
if env["brotli"]:
env_freetype.Append(CPPDEFINES=["FT_CONFIG_OPTION_USE_BROTLI"])
- if env["platform"] == "uwp":
- # Include header for UWP to fix build issues
- env_freetype.Append(CCFLAGS=["/FI", '"modules/freetype/uwpdef.h"'])
- # Globally too, as freetype is used in scene (see bottom)
- env.Append(CCFLAGS=["/FI", '"modules/freetype/uwpdef.h"'])
-
env_freetype.Prepend(CPPPATH=[thirdparty_dir + "/include"])
# Also needed in main env for scene/
env.Prepend(CPPPATH=[thirdparty_dir + "/include"])
diff --git a/modules/freetype/uwpdef.h b/modules/freetype/uwpdef.h
deleted file mode 100644
index 52b839c9b4..0000000000
--- a/modules/freetype/uwpdef.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/**************************************************************************/
-/* uwpdef.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 UWPDEF_H
-#define UWPDEF_H
-
-// "generic" is a reserved keyword in C++/CX code
-// this avoids the errors in the variable name from Freetype code
-#define generic freetype_generic
-
-#endif // UWPDEF_H
diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig
new file mode 100644
index 0000000000..640c205093
--- /dev/null
+++ b/modules/gdscript/.editorconfig
@@ -0,0 +1,8 @@
+[*.gd]
+indent_style = tab
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.out]
+insert_final_newline = true
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index dd6b668c45..806ab1e3df 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -57,11 +57,12 @@
[/codeblock]
</description>
</method>
- <method name="convert">
+ <method name="convert" is_deprecated="true">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
<param index="1" name="type" type="int" />
<description>
+ [i]Deprecated.[/i] Use [method @GlobalScope.type_convert] instead.
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
var a = [4, 2.5, 1.2]
@@ -556,7 +557,7 @@
<description>
Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting.
If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget.
- Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
+ Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
See also [constant PROPERTY_HINT_RANGE].
[codeblock]
@export_range(0, 20) var number
@@ -566,7 +567,7 @@
@export_range(0, 100, 1, "or_greater") var power_percent
@export_range(0, 100, 1, "or_greater", "or_less") var health_delta
- @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians
+ @export_range(-180, 180, 0.001, "radians_as_degrees") var angle_radians
@export_range(0, 360, 1, "degrees") var angle_degrees
@export_range(-8, 8, 2, "suffix:px") var target_offset
[/codeblock]
@@ -595,7 +596,7 @@
<return type="void" />
<param index="0" name="icon_path" type="String" />
<description>
- Add a custom icon to the current script. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs.
+ Add a custom icon to the current script. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs.
[codeblock]
@icon("res://path/to/class/icon.svg")
[/codeblock]
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 26f326838c..0b440274c0 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -36,19 +36,19 @@ using GDP = GDScriptParser;
using GDType = GDP::DataType;
static String _get_script_path(const String &p_path) {
- return vformat(R"("%s")", p_path.get_slice("://", 1));
+ return p_path.trim_prefix("res://").quote();
}
static String _get_class_name(const GDP::ClassNode &p_class) {
const GDP::ClassNode *curr_class = &p_class;
- if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return _get_script_path(curr_class->fqcn);
}
String full_name = curr_class->identifier->name;
while (curr_class->outer) {
curr_class = curr_class->outer;
- if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name);
}
full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
@@ -56,20 +56,71 @@ static String _get_class_name(const GDP::ClassNode &p_class) {
return full_name;
}
-static PropertyInfo _property_info_from_datatype(const GDType &p_type) {
- PropertyInfo pi;
- pi.type = p_type.builtin_type;
- if (p_type.kind == GDType::CLASS) {
- pi.class_name = _get_class_name(*p_type.class_type);
- } else if (p_type.kind == GDType::ENUM && p_type.enum_type != StringName()) {
- pi.type = Variant::INT; // Only int types are recognized as enums by the EditorHelp
- pi.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
- // Replace :: from enum's use of fully qualified class names with regular .
- pi.class_name = String(p_type.native_type).replace("::", ".");
- } else if (p_type.kind == GDType::NATIVE) {
- pi.class_name = p_type.native_type;
+static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false) {
+ if (!p_gdtype.is_hard_type()) {
+ r_type = "Variant";
+ return;
+ }
+ switch (p_gdtype.kind) {
+ case GDType::BUILTIN:
+ if (p_gdtype.builtin_type == Variant::NIL) {
+ 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 (!r_enum.is_empty()) {
+ r_type = "int[]";
+ r_enum += "[]";
+ return;
+ }
+ if (!r_type.is_empty() && r_type != "Variant") {
+ r_type += "[]";
+ return;
+ }
+ }
+ r_type = Variant::get_type_name(p_gdtype.builtin_type);
+ return;
+ case GDType::NATIVE:
+ r_type = p_gdtype.native_type;
+ return;
+ case GDType::SCRIPT:
+ if (p_gdtype.script_type.is_valid()) {
+ if (p_gdtype.script_type->get_global_name() != StringName()) {
+ r_type = p_gdtype.script_type->get_global_name();
+ return;
+ }
+ if (!p_gdtype.script_type->get_path().is_empty()) {
+ r_type = _get_script_path(p_gdtype.script_type->get_path());
+ return;
+ }
+ }
+ if (!p_gdtype.script_path.is_empty()) {
+ r_type = _get_script_path(p_gdtype.script_path);
+ return;
+ }
+ r_type = "Object";
+ return;
+ case GDType::CLASS:
+ r_type = _get_class_name(*p_gdtype.class_type);
+ return;
+ case GDType::ENUM:
+ r_type = "int";
+ r_enum = String(p_gdtype.native_type).replace("::", ".");
+ if (r_enum.begins_with("res://")) {
+ r_enum = r_enum.trim_prefix("res://");
+ int dot_pos = r_enum.rfind(".");
+ if (dot_pos >= 0) {
+ r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
+ }
+ }
+ return;
+ case GDType::VARIANT:
+ case GDType::RESOLVING:
+ case GDType::UNRESOLVED:
+ r_type = "Variant";
+ return;
}
- return pi;
}
void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
@@ -78,10 +129,10 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
DocData::ClassDoc &doc = p_script->doc;
doc.script_path = _get_script_path(p_script->get_script_path());
- if (p_script->name.is_empty()) {
+ if (p_script->local_name == StringName()) {
doc.name = doc.script_path;
} else {
- doc.name = p_script->name;
+ doc.name = p_script->local_name;
}
if (p_script->_owner) {
@@ -120,8 +171,8 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[class_name] = inner_class->start_line;
- // Recursively generate inner class docs
- // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts()
+ // Recursively generate inner class docs.
+ // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts().
GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class);
} break;
@@ -144,22 +195,36 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[func_name] = m_func->start_line;
- MethodInfo mi;
- mi.name = func_name;
+ DocData::MethodDoc method_doc;
+ method_doc.name = func_name;
+ method_doc.description = m_func->doc_data.description;
+ method_doc.is_deprecated = m_func->doc_data.is_deprecated;
+ method_doc.is_experimental = m_func->doc_data.is_experimental;
+ method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
- mi.return_val = _property_info_from_datatype(m_func->return_type->get_datatype());
+ _doctype_from_gdtype(m_func->return_type->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
+ } else if (!m_func->body->has_return) {
+ // If no `return` statement, then return type is `void`, not `Variant`.
+ method_doc.return_type = "void";
+ } else {
+ method_doc.return_type = "Variant";
}
+
for (const GDScriptParser::ParameterNode *p : m_func->parameters) {
- PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
- pi.name = p->identifier->name;
- mi.arguments.push_back(pi);
+ DocData::ArgumentDoc arg_doc;
+ arg_doc.name = p->identifier->name;
+ _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
+ if (p->initializer != nullptr) {
+ if (p->initializer->is_constant) {
+ arg_doc.default_value = p->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ } else {
+ arg_doc.default_value = "<unknown>";
+ }
+ }
+ method_doc.arguments.push_back(arg_doc);
}
- DocData::MethodDoc method_doc;
- DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_data.description);
- method_doc.is_deprecated = m_func->doc_data.is_deprecated;
- method_doc.is_experimental = m_func->doc_data.is_experimental;
doc.methods.push_back(method_doc);
} break;
@@ -169,18 +234,19 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[signal_name] = m_signal->start_line;
- MethodInfo mi;
- mi.name = signal_name;
- for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
- PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
- pi.name = p->identifier->name;
- mi.arguments.push_back(pi);
- }
-
DocData::MethodDoc signal_doc;
- DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_data.description);
+ signal_doc.name = signal_name;
+ signal_doc.description = m_signal->doc_data.description;
signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
signal_doc.is_experimental = m_signal->doc_data.is_experimental;
+
+ for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
+ DocData::ArgumentDoc arg_doc;
+ arg_doc.name = p->identifier->name;
+ _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
+ signal_doc.arguments.push_back(arg_doc);
+ }
+
doc.signals.push_back(signal_doc);
} break;
@@ -191,50 +257,41 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[var_name] = m_var->start_line;
DocData::PropertyDoc prop_doc;
-
prop_doc.name = var_name;
prop_doc.description = m_var->doc_data.description;
prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
prop_doc.is_experimental = m_var->doc_data.is_experimental;
+ _doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
- GDType dt = m_var->get_datatype();
- switch (dt.kind) {
- case GDType::CLASS:
- prop_doc.type = _get_class_name(*dt.class_type);
+ switch (m_var->property) {
+ case GDP::VariableNode::PROP_NONE:
break;
- case GDType::VARIANT:
- prop_doc.type = "Variant";
+ case GDP::VariableNode::PROP_INLINE:
+ if (m_var->setter != nullptr) {
+ prop_doc.setter = m_var->setter->identifier->name;
+ }
+ if (m_var->getter != nullptr) {
+ prop_doc.getter = m_var->getter->identifier->name;
+ }
break;
- case GDType::ENUM:
- prop_doc.type = Variant::get_type_name(dt.builtin_type);
- // Replace :: from enum's use of fully qualified class names with regular .
- prop_doc.enumeration = String(dt.native_type).replace("::", ".");
- break;
- case GDType::NATIVE:;
- prop_doc.type = dt.native_type;
- break;
- case GDType::BUILTIN:
- prop_doc.type = Variant::get_type_name(dt.builtin_type);
- break;
- default:
- // SCRIPT: can be preload()'d and perhaps used as types directly?
- // RESOLVING & UNRESOLVED should never happen since docgen requires analyzing w/o errors
+ case GDP::VariableNode::PROP_SETGET:
+ if (m_var->setter_pointer != nullptr) {
+ prop_doc.setter = m_var->setter_pointer->name;
+ }
+ if (m_var->getter_pointer != nullptr) {
+ prop_doc.getter = m_var->getter_pointer->name;
+ }
break;
}
- if (m_var->property == GDP::VariableNode::PROP_SETGET) {
- if (m_var->setter_pointer != nullptr) {
- prop_doc.setter = m_var->setter_pointer->name;
- }
- if (m_var->getter_pointer != nullptr) {
- prop_doc.getter = m_var->getter_pointer->name;
+ if (m_var->initializer) {
+ if (m_var->initializer->is_constant) {
+ prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ } else {
+ prop_doc.default_value = "<unknown>";
}
}
- if (m_var->initializer && m_var->initializer->is_constant) {
- prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "");
- }
-
prop_doc.overridden = false;
doc.properties.push_back(prop_doc);
@@ -280,8 +337,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
doc.constants.push_back(const_doc);
} break;
- case GDP::ClassNode::Member::GROUP:
- case GDP::ClassNode::Member::UNDEFINED:
+
default:
break;
}
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index b54dc502ae..1be690d894 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
+ bool in_raw_string = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
@@ -145,6 +146,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
+ // Don't skip comments, for highlighting markers.
+ if (color_regions[in_region].start_key == "#") {
+ break;
+ }
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
if (str.find("\\", from) >= 0) {
@@ -163,7 +168,8 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
break;
}
- if (j == line_length) {
+ // Don't skip comments, for highlighting markers.
+ if (j == line_length && color_regions[in_region].start_key != "#") {
continue;
}
}
@@ -185,56 +191,101 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
- // Search the line.
- int region_end_index = -1;
- int end_key_length = color_regions[in_region].end_key.length();
- const char32_t *end_key = color_regions[in_region].end_key.get_data();
- for (; from < line_length; from++) {
- if (line_length - from < end_key_length) {
- // Don't break if '\' to highlight esc chars.
- if (str.find("\\", from) < 0) {
- break;
+ if (color_regions[in_region].start_key == "#") {
+ int marker_start_pos = from;
+ int marker_len = 0;
+ while (from <= line_length) {
+ if (from < line_length && is_unicode_identifier_continue(str[from])) {
+ marker_len++;
+ } else {
+ if (marker_len > 0) {
+ HashMap<String, CommentMarkerLevel>::ConstIterator E = comment_markers.find(str.substr(marker_start_pos, marker_len));
+ if (E) {
+ Dictionary marker_highlighter_info;
+ marker_highlighter_info["color"] = comment_marker_colors[E->value];
+ color_map[marker_start_pos] = marker_highlighter_info;
+
+ Dictionary marker_continue_highlighter_info;
+ marker_continue_highlighter_info["color"] = region_color;
+ color_map[from] = marker_continue_highlighter_info;
+ }
+ }
+ marker_start_pos = from + 1;
+ marker_len = 0;
}
+ from++;
}
+ from = line_length - 1;
+ j = from;
+ } else {
+ // Search the line.
+ int region_end_index = -1;
+ int end_key_length = color_regions[in_region].end_key.length();
+ const char32_t *end_key = color_regions[in_region].end_key.get_data();
+ for (; from < line_length; from++) {
+ if (line_length - from < end_key_length) {
+ // Don't break if '\' to highlight esc chars.
+ if (str.find("\\", from) < 0) {
+ break;
+ }
+ }
- if (!is_symbol(str[from])) {
- continue;
- }
+ if (!is_symbol(str[from])) {
+ continue;
+ }
- if (str[from] == '\\') {
- Dictionary escape_char_highlighter_info;
- escape_char_highlighter_info["color"] = symbol_color;
- color_map[from] = escape_char_highlighter_info;
+ if (str[from] == '\\') {
+ if (!in_raw_string) {
+ Dictionary escape_char_highlighter_info;
+ escape_char_highlighter_info["color"] = symbol_color;
+ color_map[from] = escape_char_highlighter_info;
+ }
- from++;
+ from++;
+
+ if (!in_raw_string) {
+ int esc_len = 0;
+ if (str[from] == 'u') {
+ esc_len = 4;
+ } else if (str[from] == 'U') {
+ esc_len = 6;
+ }
+ for (int k = 0; k < esc_len && from < line_length - 1; k++) {
+ if (!is_hex_digit(str[from + 1])) {
+ break;
+ }
+ from++;
+ }
+
+ Dictionary region_continue_highlighter_info;
+ region_continue_highlighter_info["color"] = region_color;
+ color_map[from + 1] = region_continue_highlighter_info;
+ }
- Dictionary region_continue_highlighter_info;
- prev_color = region_color;
- region_continue_highlighter_info["color"] = region_color;
- color_map[from + 1] = region_continue_highlighter_info;
- continue;
- }
+ continue;
+ }
- region_end_index = from;
- for (int k = 0; k < end_key_length; k++) {
- if (end_key[k] != str[from + k]) {
- region_end_index = -1;
+ region_end_index = from;
+ for (int k = 0; k < end_key_length; k++) {
+ if (end_key[k] != str[from + k]) {
+ region_end_index = -1;
+ break;
+ }
+ }
+
+ if (region_end_index != -1) {
break;
}
}
-
- if (region_end_index != -1) {
- break;
+ j = from + (end_key_length - 1);
+ if (region_end_index == -1) {
+ color_region_cache[p_line] = in_region;
}
}
prev_type = REGION;
prev_text = "";
prev_column = j;
- j = from + (end_key_length - 1);
- if (region_end_index == -1) {
- color_region_cache[p_line] = in_region;
- }
in_region = -1;
prev_is_char = false;
@@ -272,7 +323,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Allow ABCDEF in hex notation.
if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
is_a_digit = true;
- } else {
+ } else if (str[j] != '_') {
is_hex_notation = false;
}
@@ -295,14 +346,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
!(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
- !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
+ !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
/* This condition continues number highlighting in special cases.
1st row: '+' or '-' after scientific notation (like 3e-4);
2nd row: '_' as a numeric separator;
3rd row: Scientific notation 'e' and floating points;
4th row: Floating points inside the number, or leading if after a unary mathematical operator;
- 5th row: Multiple unary mathematical operators (like ~-7)*/
+ 5th row: Multiple unary mathematical operators (like ~-7) */
in_number = false;
}
} else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
@@ -457,6 +508,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_member_variable = false;
}
+ if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) {
+ in_raw_string = true;
+ } else if (in_raw_string && in_region == -1) {
+ in_raw_string = false;
+ }
+
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
@@ -488,7 +545,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_annotation = false;
}
- if (in_node_ref) {
+ if (in_raw_string) {
+ color = string_color;
+ } else if (in_node_ref) {
next_type = NODE_REF;
color = node_ref_color;
} else if (in_annotation) {
@@ -660,7 +719,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
/* Strings */
- const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
List<String> strings;
gdscript->get_string_delimiters(&strings);
for (const String &string : strings) {
@@ -706,6 +765,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
node_ref_color = Color(0.39, 0.76, 0.35);
annotation_color = Color(1.0, 0.7, 0.45);
string_name_color = Color(1.0, 0.76, 0.65);
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.77, 0.35, 0.35);
+ comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.72, 0.61, 0.48);
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.56, 0.67, 0.51);
} else {
function_definition_color = Color(0, 0.6, 0.6);
global_function_color = Color(0.36, 0.18, 0.72);
@@ -713,6 +775,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
node_ref_color = Color(0.0, 0.5, 0);
annotation_color = Color(0.8, 0.37, 0);
string_name_color = Color(0.8, 0.56, 0.45);
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.8, 0.14, 0.14);
+ comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.75, 0.39, 0.03);
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
@@ -721,6 +786,14 @@ void GDScriptSyntaxHighlighter::_update_cache() {
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_reference_color", node_ref_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/string_name_color", string_name_color);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_color", comment_marker_colors[COMMENT_MARKER_CRITICAL]);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_color", comment_marker_colors[COMMENT_MARKER_WARNING]);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_color", comment_marker_colors[COMMENT_MARKER_NOTICE]);
+ // The list is based on <https://github.com/KDE/syntax-highlighting/blob/master/data/syntax/alert.xml>.
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_list", "ALERT,ATTENTION,CAUTION,CRITICAL,DANGER,SECURITY");
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_list", "BUG,DEPRECATED,FIXME,HACK,TASK,TBD,TODO,WARNING");
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_list", "INFO,NOTE,NOTICE,TEST,TESTING");
+
if (text_edit_color_theme == "Default" || godot_2_theme) {
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/function_definition_color",
@@ -746,6 +819,18 @@ void GDScriptSyntaxHighlighter::_update_cache() {
"text_editor/theme/highlighting/gdscript/string_name_color",
string_name_color,
true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/critical_color",
+ comment_marker_colors[COMMENT_MARKER_CRITICAL],
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/warning_color",
+ comment_marker_colors[COMMENT_MARKER_WARNING],
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/notice_color",
+ comment_marker_colors[COMMENT_MARKER_NOTICE],
+ true);
}
function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
@@ -755,6 +840,23 @@ void GDScriptSyntaxHighlighter::_update_cache() {
annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
+ comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
+
+ comment_markers.clear();
+ Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
+ for (int i = 0; i < critical_list.size(); i++) {
+ comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
+ }
+ Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
+ for (int i = 0; i < warning_list.size(); i++) {
+ comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
+ }
+ Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
+ for (int i = 0; i < notice_list.size(); i++) {
+ comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
+ }
}
void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index aceb644658..090857f397 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -78,12 +78,22 @@ private:
Color built_in_type_color;
Color number_color;
Color member_color;
+ Color string_color;
Color node_path_color;
Color node_ref_color;
Color annotation_color;
Color string_name_color;
Color type_color;
+ enum CommentMarkerLevel {
+ COMMENT_MARKER_CRITICAL,
+ COMMENT_MARKER_WARNING,
+ COMMENT_MARKER_NOTICE,
+ COMMENT_MARKER_MAX,
+ };
+ Color comment_marker_colors[COMMENT_MARKER_MAX];
+ HashMap<String, CommentMarkerLevel> comment_markers;
+
void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false);
public:
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 064143f400..becc2876f9 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -225,10 +225,15 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
assignee_name = static_cast<const GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
} else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
- assignee_name = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee);
+ if (subscript->is_attribute && subscript->attribute) {
+ assignee_name = subscript->attribute->name;
+ } else if (subscript->index && _is_constant_string(subscript->index)) {
+ assignee_name = subscript->index->reduced_value;
+ }
}
- if (assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
+ if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
ids->push_back(p_assignment->assigned_value->reduced_value);
} else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
@@ -236,7 +241,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
// get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_assignment->assigned_value);
- if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ if (!call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
// Extract the name in "extension ; name" of PackedStringArray.
diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
index b8fc8c75dc..28ab080dd2 100644
--- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
+++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd
@@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
- # Handle Jump.
+ # Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
index 53bc606c9a..9b0e4be4ed 100644
--- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
+++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd
@@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= gravity * delta
- # Handle Jump.
+ # Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
diff --git a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd
index b27b3e5655..547943b910 100644
--- a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd
+++ b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd
@@ -1,6 +1,7 @@
# meta-description: Basic plugin template
+
@tool
-extends EditorPlugin
+extends _BASE_
func _enter_tree() -> void:
diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd
index 556afe994b..6772ea4a26 100644
--- a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd
+++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd
@@ -1,6 +1,7 @@
# meta-description: Basic import script template
+
@tool
-extends EditorScenePostImport
+extends _BASE_
# Called by the editor when a scene has this script set as the import script in the import tab.
diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd
index 875afb4fc0..e8f907f43b 100644
--- a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd
+++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd
@@ -1,6 +1,7 @@
# meta-description: Basic import script template (no comments)
+
@tool
-extends EditorScenePostImport
+extends _BASE_
func _post_import(scene: Node) -> Object:
diff --git a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd
index fdb8550d43..fee7353f0d 100644
--- a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd
+++ b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd
@@ -1,6 +1,7 @@
# meta-description: Basic editor script template
+
@tool
-extends EditorScript
+extends _BASE_
# Called when the script is executed (using File -> Run in Script Editor).
diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd
index c79eeb91ec..c7a999ef24 100644
--- a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd
+++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd
@@ -1,15 +1,16 @@
# meta-description: Base template for rich text effects
@tool
-class_name _CLASS_
+# Having a class name is handy for picking the effect in the Inspector.
+class_name RichText_CLASS_
extends _BASE_
# To use this effect:
# - Enable BBCode on a RichTextLabel.
# - Register this effect on the label.
-# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text.
-var bbcode := "_CLASS_"
+# - Use [_CLASS_SNAKE_CASE_ param=2.0]hello[/_CLASS_SNAKE_CASE_] in text.
+var bbcode := "_CLASS_SNAKE_CASE_"
func _process_custom_fx(char_fx: CharFXTransform) -> bool:
diff --git a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd
index 283a95d3b4..458e22dae4 100644
--- a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd
+++ b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd
@@ -1,6 +1,7 @@
# meta-description: Visual shader's node plugin template
@tool
+# Having a class name is required for a custom node.
class_name VisualShaderNode_CLASS_
extends _BASE_
@@ -17,7 +18,7 @@ func _get_description() -> String:
return ""
-func _get_return_icon_type() -> int:
+func _get_return_icon_type() -> PortType:
return PORT_TYPE_SCALAR
@@ -29,7 +30,7 @@ func _get_input_port_name(port: int) -> String:
return ""
-func _get_input_port_type(port: int) -> int:
+func _get_input_port_type(port: int) -> PortType:
return PORT_TYPE_SCALAR
@@ -41,10 +42,10 @@ func _get_output_port_name(port: int) -> String:
return "result"
-func _get_output_port_type(port: int) -> int:
+func _get_output_port_type(port: int) -> PortType:
return PORT_TYPE_SCALAR
func _get_code(input_vars: Array[String], output_vars: Array[String],
- mode: int, type: int) -> String:
+ mode: Shader.Mode, type: VisualShader.Type) -> String:
return output_vars[0] + " = 0.0;"
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 0bf9f72a2c..b5c80d9e2d 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -83,7 +83,7 @@ void GDScriptNativeClass::_bind_methods() {
Variant GDScriptNativeClass::_new() {
Object *o = instantiate();
- ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable.");
+ ERR_FAIL_NULL_V_MSG(o, Variant(), "Class type: '" + String(name) + "' is not instantiable.");
RefCounted *rc = Object::cast_to<RefCounted>(o);
if (rc) {
@@ -215,7 +215,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr
} else {
owner = memnew(RefCounted); //by default, no base means use reference
}
- ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class.");
+ ERR_FAIL_NULL_V_MSG(owner, Variant(), "Can't inherit from a virtual class.");
RefCounted *r = Object::cast_to<RefCounted>(owner);
if (r) {
@@ -254,7 +254,7 @@ Ref<Script> GDScript::get_base_script() const {
}
StringName GDScript::get_global_name() const {
- return name;
+ return global_name;
}
StringName GDScript::get_instance_base_type() const {
@@ -284,27 +284,9 @@ void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_
const GDScript *current = this;
while (current) {
for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) {
- GDScriptFunction *func = E.value;
- MethodInfo mi;
- mi.name = E.key;
-
- if (func->is_static()) {
- mi.flags |= METHOD_FLAG_STATIC;
- }
-
- for (int i = 0; i < func->get_argument_count(); i++) {
- PropertyInfo arginfo = func->get_argument_type(i);
-#ifdef TOOLS_ENABLED
- arginfo.name = func->get_argument_name(i);
-#endif
- mi.arguments.push_back(arginfo);
- }
-#ifdef TOOLS_ENABLED
- mi.default_arguments.append_array(func->get_default_arg_values());
-#endif
- mi.return_val = func->get_return_type();
- r_list->push_back(mi);
+ r_list->push_back(E.value->get_method_info());
}
+
if (!p_include_base) {
return;
}
@@ -323,10 +305,12 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl
while (sptr) {
Vector<_GDScriptMemberSort> msort;
- for (const KeyValue<StringName, PropertyInfo> &E : sptr->member_info) {
+ for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) {
+ if (!sptr->members.has(E.key)) {
+ continue; // Skip base class members.
+ }
_GDScriptMemberSort ms;
- ERR_CONTINUE(!sptr->member_indices.has(E.key));
- ms.index = sptr->member_indices[E.key].index;
+ ms.index = E.value.index;
ms.name = E.key;
msort.push_back(ms);
}
@@ -334,7 +318,7 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl
msort.sort();
msort.reverse();
for (int i = 0; i < msort.size(); i++) {
- props.push_front(sptr->member_info[msort[i].name]);
+ props.push_front(sptr->member_indices[msort[i].name].property_info);
}
#ifdef TOOLS_ENABLED
@@ -368,15 +352,7 @@ MethodInfo GDScript::get_method_info(const StringName &p_method) const {
return MethodInfo();
}
- GDScriptFunction *func = E->value;
- MethodInfo mi;
- mi.name = E->key;
- for (int i = 0; i < func->get_argument_count(); i++) {
- mi.arguments.push_back(func->get_argument_type(i));
- }
-
- mi.return_val = func->get_return_type();
- return mi;
+ return E->value->get_method_info();
}
bool GDScript::get_property_default_value(const StringName &p_property, Variant &r_value) const {
@@ -482,6 +458,10 @@ void GDScript::_clear_doc() {
docs.clear();
doc = DocData::ClassDoc();
}
+
+String GDScript::get_class_icon_path() const {
+ return simplified_icon_path;
+}
#endif
bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) {
@@ -553,13 +533,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
member_default_values_cache[member.variable->identifier->name] = default_value;
} break;
case GDScriptParser::ClassNode::Member::SIGNAL: {
- // TODO: Cache this in parser to avoid loops like this.
- Vector<StringName> parameters_names;
- parameters_names.resize(member.signal->parameters.size());
- for (int j = 0; j < member.signal->parameters.size(); j++) {
- parameters_names.write[j] = member.signal->parameters[j]->identifier->name;
- }
- _signals[member.signal->identifier->name] = parameters_names;
+ _signals[member.signal->identifier->name] = member.signal->method_info;
} break;
case GDScriptParser::ClassNode::Member::GROUP: {
members_cache.push_back(member.annotation->export_info);
@@ -973,22 +947,26 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
- List<PropertyInfo> property_list;
-
+ List<const GDScript *> classes;
const GDScript *top = this;
while (top) {
- for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) {
- PropertyInfo pi = PropertyInfo(E.value.data_type);
- pi.name = E.key;
- pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property.
- property_list.push_back(pi);
- }
-
+ classes.push_back(top);
top = top->_base;
}
- for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) {
- p_properties->push_back(E->get());
+ for (const List<const GDScript *>::Element *E = classes.back(); E; E = E->prev()) {
+ Vector<_GDScriptMemberSort> msort;
+ for (const KeyValue<StringName, MemberInfo> &F : E->get()->static_variables_indices) {
+ _GDScriptMemberSort ms;
+ ms.index = F.value.index;
+ ms.name = F.key;
+ msort.push_back(ms);
+ }
+ msort.sort();
+
+ for (int i = 0; i < msort.size(); i++) {
+ p_properties->push_back(E->get()->static_variables_indices[msort[i].name].property_info);
+ }
}
}
@@ -1106,7 +1084,7 @@ GDScript *GDScript::find_class(const String &p_qualified_name) {
Vector<String> class_names;
GDScript *result = nullptr;
// Empty initial name means start here.
- if (first.is_empty() || first == name) {
+ if (first.is_empty() || first == global_name) {
class_names = p_qualified_name.split("::");
result = this;
} else if (p_qualified_name.begins_with(get_root_script()->path)) {
@@ -1241,15 +1219,8 @@ bool GDScript::has_script_signal(const StringName &p_signal) const {
}
void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const {
- for (const KeyValue<StringName, Vector<StringName>> &E : _signals) {
- MethodInfo mi;
- mi.name = E.key;
- for (int i = 0; i < E.value.size(); i++) {
- PropertyInfo arg;
- arg.name = E.value[i];
- mi.arguments.push_back(arg);
- }
- r_list->push_back(mi);
+ for (const KeyValue<StringName, MethodInfo> &E : _signals) {
+ r_list->push_back(E.value);
}
if (!p_include_base) {
@@ -1270,21 +1241,6 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
_get_script_signal_list(r_signals, true);
}
-String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) {
- ERR_FAIL_NULL_V(p_gdscript, String());
-
- String class_name;
- while (p_gdscript) {
- if (class_name.is_empty()) {
- class_name = p_gdscript->get_script_class_name();
- } else {
- class_name = p_gdscript->get_script_class_name() + "." + class_name;
- }
- p_gdscript = p_gdscript->_owner;
- }
- return class_name;
-}
-
GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
Object *obj = p_variant;
if (obj == nullptr || obj->get_instance_id().is_null()) {
@@ -1395,29 +1351,13 @@ void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) {
}
}
-void GDScript::_init_rpc_methods_properties() {
- // Copy the base rpc methods so we don't mask their IDs.
- rpc_config.clear();
- if (base.is_valid()) {
- rpc_config = base->rpc_config.duplicate();
- }
-
- // RPC Methods
- for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- Variant config = E.value->get_rpc_config();
- if (config.get_type() != Variant::NIL) {
- rpc_config[E.value->get_name()] = config;
- }
- }
-}
-
#ifdef DEBUG_ENABLED
String GDScript::debug_get_script_name(const Ref<Script> &p_script) {
if (p_script.is_valid()) {
Ref<GDScript> gdscript = p_script;
if (gdscript.is_valid()) {
- if (!gdscript->get_script_class_name().is_empty()) {
- return gdscript->get_script_class_name();
+ if (gdscript->get_local_name() != StringName()) {
+ return gdscript->get_local_name();
}
return gdscript->get_fully_qualified_name().get_file();
}
@@ -1663,7 +1603,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
{
- HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name);
+ HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name);
if (E) {
r_ret = Signal(this->owner, E->key);
return true;
@@ -1711,15 +1651,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
- const GDScript *sptr = script.ptr();
- while (sptr) {
- if (sptr->member_info.has(p_name)) {
- if (r_is_valid) {
- *r_is_valid = true;
- }
- return sptr->member_info[p_name].type;
+ if (script->member_indices.has(p_name)) {
+ if (r_is_valid) {
+ *r_is_valid = true;
}
- sptr = sptr->_base;
+ return script->member_indices[p_name].property_info.type;
}
if (r_is_valid) {
@@ -1728,6 +1664,25 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
return Variant::NIL;
}
+void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
+ Variant property = (Dictionary)p_property;
+ const Variant *args[1] = { &property };
+
+ const GDScript *sptr = script.ptr();
+ while (sptr) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK) {
+ p_property = PropertyInfo::from_dict(property);
+ return;
+ }
+ }
+ sptr = sptr->_base;
+ }
+}
+
void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
// exported members, not done yet!
@@ -1775,10 +1730,12 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
//instance a fake script for editing the values
Vector<_GDScriptMemberSort> msort;
- for (const KeyValue<StringName, PropertyInfo> &F : sptr->member_info) {
+ for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) {
+ if (!sptr->members.has(F.key)) {
+ continue; // Skip base class members.
+ }
_GDScriptMemberSort ms;
- ERR_CONTINUE(!sptr->member_indices.has(F.key));
- ms.index = sptr->member_indices[F.key].index;
+ ms.index = F.value.index;
ms.name = F.key;
msort.push_back(ms);
}
@@ -1786,14 +1743,15 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
msort.sort();
msort.reverse();
for (int i = 0; i < msort.size(); i++) {
- props.push_front(sptr->member_info[msort[i].name]);
+ props.push_front(sptr->member_indices[msort[i].name].property_info);
}
#ifdef TOOLS_ENABLED
p_properties->push_back(sptr->get_class_category());
#endif // TOOLS_ENABLED
- for (const PropertyInfo &prop : props) {
+ for (PropertyInfo &prop : props) {
+ validate_property(prop);
p_properties->push_back(prop);
}
@@ -1848,12 +1806,7 @@ void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
const GDScript *sptr = script.ptr();
while (sptr) {
for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) {
- MethodInfo mi;
- mi.name = E.key;
- for (int i = 0; i < E.value->get_argument_count(); i++) {
- mi.arguments.push_back(PropertyInfo(Variant::NIL, "arg" + itos(i)));
- }
- p_list->push_back(mi);
+ p_list->push_back(E.value->get_method_info());
}
sptr = sptr->_base;
}
@@ -1897,14 +1850,23 @@ Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_ar
return Variant();
}
-void GDScriptInstance::notification(int p_notification) {
+void GDScriptInstance::notification(int p_notification, bool p_reversed) {
//notification is not virtual, it gets called at ALL levels just like in C.
Variant value = p_notification;
const Variant *args[1] = { &value };
+ List<GDScript *> pl;
GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
+ if (p_reversed) {
+ pl.push_back(sptr);
+ } else {
+ pl.push_front(sptr);
+ }
+ sptr = sptr->_base;
+ }
+ for (GDScript *sc : pl) {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
if (E) {
Callable::CallError err;
E->value->call(this, args, 1, err);
@@ -1912,7 +1874,6 @@ void GDScriptInstance::notification(int p_notification) {
//print error about notification call
}
}
- sptr = sptr->_base;
}
}
@@ -2456,6 +2417,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"return",
"match",
"while",
+ "when",
// These keywords are not implemented currently, but reserved for (potential) future use.
// We highlight them as keywords to make errors easier to understand.
"trait",
@@ -2489,6 +2451,7 @@ bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const {
p_keyword == "match" ||
p_keyword == "pass" ||
p_keyword == "return" ||
+ p_keyword == "when" ||
p_keyword == "while";
}
@@ -2527,13 +2490,6 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
* Before changing this function, please ask the current maintainer of EditorFileSystem.
*/
- if (r_icon_path) {
- if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
- *r_icon_path = c->icon_path.simplify_path();
- } else if (c->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
- }
- }
if (r_base_type) {
const GDScriptParser::ClassNode *subclass = c;
String path = p_path;
@@ -2601,6 +2557,9 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
}
}
}
+ if (r_icon_path) {
+ *r_icon_path = c->simplified_icon_path;
+ }
return c->identifier != nullptr ? String(c->identifier->name) : String();
}
@@ -2616,6 +2575,7 @@ GDScriptLanguage::GDScriptLanguage() {
strings._set = StaticCString::create("_set");
strings._get = StaticCString::create("_get");
strings._get_property_list = StaticCString::create("_get_property_list");
+ strings._validate_property = StaticCString::create("_validate_property");
strings._property_can_revert = StaticCString::create("_property_can_revert");
strings._property_get_revert = StaticCString::create("_property_get_revert");
strings._script_source = StaticCString::create("script/source");
@@ -2695,6 +2655,11 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
Error err;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
+ if (err && scr.is_valid()) {
+ // If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error.
+ ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)", p_path, error_names[err]));
+ }
+
if (r_error) {
// Don't fail loading because of parsing error.
*r_error = scr.is_valid() ? OK : err;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index c41b1a0def..50ccfabcc1 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -69,6 +69,7 @@ class GDScript : public Script {
StringName setter;
StringName getter;
GDScriptDataType data_type;
+ PropertyInfo property_info;
};
struct ClearData {
@@ -93,14 +94,18 @@ class GDScript : public Script {
GDScript *_base = nullptr; //fast pointer access
GDScript *_owner = nullptr; //for subclasses
- HashSet<StringName> members; //members are just indices to the instantiated script.
- HashMap<StringName, Variant> constants;
+ // Members are just indices to the instantiated script.
+ HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes.
+ HashSet<StringName> members; // Only members of the current class.
+
+ // Only static variables of the current class.
HashMap<StringName, MemberInfo> static_variables_indices;
- Vector<Variant> static_variables;
+ Vector<Variant> static_variables; // Static variable values.
+
+ HashMap<StringName, Variant> constants;
HashMap<StringName, GDScriptFunction *> member_functions;
- HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
HashMap<StringName, Ref<GDScript>> subclasses;
- HashMap<StringName, Vector<StringName>> _signals;
+ HashMap<StringName, MethodInfo> _signals;
Dictionary rpc_config;
#ifdef TOOLS_ENABLED
@@ -126,8 +131,6 @@ class GDScript : public Script {
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif
- HashMap<StringName, PropertyInfo> member_info;
-
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
GDScriptFunction *implicit_ready = nullptr;
@@ -142,8 +145,10 @@ class GDScript : public Script {
//exported members
String source;
String path;
- String name;
+ StringName local_name; // Inner class identifier or `class_name`.
+ StringName global_name; // `class_name`.
String fully_qualified_name;
+ String simplified_icon_path;
SelfList<GDScript> script_list;
SelfList<GDScriptFunctionState>::List pending_func_states;
@@ -167,15 +172,11 @@ class GDScript : public Script {
bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr);
void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data);
- void _init_rpc_methods_properties();
void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const;
void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const;
void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const;
- // This method will map the class name from "RefCounted" to "MyClass.InnerClass".
- static String _get_gdscript_reference_class_name(const GDScript *p_gdscript);
-
GDScript *_get_gdscript_from_variant(const Variant &p_variant);
void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
@@ -193,9 +194,12 @@ public:
static String debug_get_script_name(const Ref<Script> &p_script);
#endif
+ _FORCE_INLINE_ StringName get_local_name() const { return local_name; }
+
void clear(GDScript::ClearData *p_clear_data = nullptr);
virtual bool is_valid() const override { return valid; }
+ virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes.
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -213,7 +217,6 @@ public:
}
const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
const Ref<GDScriptNativeClass> &get_native() const { return native; }
- const String &get_script_class_name() const { return name; }
RBSet<GDScript *> get_dependencies();
RBSet<GDScript *> get_inverted_dependencies();
@@ -250,6 +253,7 @@ public:
virtual Vector<DocData::ClassDoc> get_documentation() const override {
return docs;
}
+ virtual String get_class_icon_path() const override;
#endif // TOOLS_ENABLED
virtual Error reload(bool p_keep_state = false) override;
@@ -319,6 +323,7 @@ public:
virtual bool get(const StringName &p_name, Variant &r_ret) const;
virtual void get_property_list(List<PropertyInfo> *p_properties) const;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const;
+ virtual void validate_property(PropertyInfo &p_property) const;
virtual bool property_can_revert(const StringName &p_name) const;
virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const;
@@ -329,7 +334,7 @@ public:
Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
- virtual void notification(int p_notification);
+ virtual void notification(int p_notification, bool p_reversed = false);
String to_string(bool *r_valid);
virtual Ref<Script> get_script() const;
@@ -466,6 +471,7 @@ public:
StringName _set;
StringName _get;
StringName _get_property_list;
+ StringName _validate_property;
StringName _property_can_revert;
StringName _property_get_revert;
StringName _script_source;
@@ -500,7 +506,9 @@ public:
virtual Vector<ScriptTemplate> get_built_in_templates(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;
- virtual bool has_named_classes() const override;
+#ifndef DISABLE_DEPRECATED
+ virtual bool has_named_classes() const override { return false; }
+#endif
virtual bool supports_builtin_mode() const override;
virtual bool supports_documentation() const override;
virtual bool can_inherit_from_file() const override { return true; }
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index cb04913620..882c246706 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
return ERR_PARSE_ERROR;
}
- if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) {
+ if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) {
push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node);
return ERR_PARSE_ERROR;
}
@@ -459,6 +459,10 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
}
base = info_parser->get_parser()->head->get_datatype();
} else if (class_exists(name)) {
+ if (Engine::get_singleton()->has_singleton(name)) {
+ push_error(vformat(R"(Cannot inherit native class "%s" because it is an engine singleton.)", name), id);
+ return ERR_PARSE_ERROR;
+ }
base.kind = GDScriptParser::DataType::NATIVE;
base.native_type = name;
} else {
@@ -613,144 +617,178 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return result;
}
- StringName first = p_type->type_chain[0]->name;
+ const GDScriptParser::IdentifierNode *first_id = p_type->type_chain[0];
+ StringName first = first_id->name;
+ bool type_found = false;
- if (first == SNAME("Variant")) {
- if (p_type->type_chain.size() == 2) {
- // May be nested enum.
- StringName enum_name = p_type->type_chain[1]->name;
- StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
- if (CoreConstants::is_global_enum(qualified_name)) {
- result = make_global_enum_type(enum_name, first, true);
- return result;
+ if (first_id->suite && first_id->suite->has_local(first)) {
+ const GDScriptParser::SuiteNode::Local &local = first_id->suite->get_local(first);
+ if (local.type == GDScriptParser::SuiteNode::Local::CONSTANT) {
+ result = local.get_datatype();
+ if (!result.is_set()) {
+ // Don't try to resolve it as the constant can be declared below.
+ push_error(vformat(R"(Local constant "%s" is not resolved at this point.)", first), first_id);
+ return bad_type;
+ }
+ if (result.is_meta_type) {
+ type_found = true;
+ } else if (Ref<Script>(local.constant->initializer->reduced_value).is_valid()) {
+ Ref<GDScript> gdscript = local.constant->initializer->reduced_value;
+ if (gdscript.is_valid()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(local.constant->initializer->reduced_value);
+ }
+ type_found = true;
} else {
- push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
+ push_error(vformat(R"(Local constant "%s" is not a valid type.)", first), first_id);
return bad_type;
}
- } else if (p_type->type_chain.size() > 2) {
- push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
- return bad_type;
- }
- result.kind = GDScriptParser::DataType::VARIANT;
- } else if (first == SNAME("Object")) {
- // Object is treated like a native type, not a built-in.
- result.kind = GDScriptParser::DataType::NATIVE;
- result.builtin_type = Variant::OBJECT;
- result.native_type = SNAME("Object");
- } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
- // Built-in types.
- if (p_type->type_chain.size() > 1) {
- push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
+ } else {
+ push_error(vformat(R"(Local %s "%s" cannot be used as a type.)", local.get_name(), first), first_id);
return bad_type;
}
- result.kind = GDScriptParser::DataType::BUILTIN;
- 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));
- if (container_type.kind != GDScriptParser::DataType::VARIANT) {
- container_type.is_constant = false;
- result.set_container_element_type(container_type);
- }
- }
- } else if (class_exists(first)) {
- // Native engine classes.
- result.kind = GDScriptParser::DataType::NATIVE;
- result.builtin_type = Variant::OBJECT;
- result.native_type = first;
- } else if (ScriptServer::is_global_class(first)) {
- if (parser->script_path == ScriptServer::get_global_class_path(first)) {
- result = parser->head->get_datatype();
- } else {
- String path = ScriptServer::get_global_class_path(first);
- String ext = path.get_extension();
- if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
- Ref<GDScriptParserRef> ref = get_parser_for(path);
- if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
+ if (!type_found) {
+ if (first == SNAME("Variant")) {
+ if (p_type->type_chain.size() == 2) {
+ // May be nested enum.
+ StringName enum_name = p_type->type_chain[1]->name;
+ StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
+ if (CoreConstants::is_global_enum(qualified_name)) {
+ result = make_global_enum_type(enum_name, first, true);
+ return result;
+ } else {
+ push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
return bad_type;
}
- result = ref->get_parser()->head->get_datatype();
- } else {
- result = make_script_meta_type(ResourceLoader::load(path, "Script"));
+ } else if (p_type->type_chain.size() > 2) {
+ push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
+ return bad_type;
}
- }
- } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
- const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
- Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
- if (ref.is_null()) {
- push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
- return bad_type;
- }
- if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
- return bad_type;
- }
- result = ref->get_parser()->head->get_datatype();
- } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
- // Native enum in current class.
- result = make_native_enum_type(first, parser->current_class->base_type.native_type);
- } else if (CoreConstants::is_global_enum(first)) {
- if (p_type->type_chain.size() > 1) {
- push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
- return bad_type;
- }
- result = make_global_enum_type(first, StringName());
- } else {
- // Classes in current scope.
- List<GDScriptParser::ClassNode *> script_classes;
- bool found = false;
- get_class_node_current_scope_classes(parser->current_class, &script_classes);
- for (GDScriptParser::ClassNode *script_class : script_classes) {
- if (found) {
- break;
+ result.kind = GDScriptParser::DataType::VARIANT;
+ } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
+ // Built-in types.
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
+ return bad_type;
}
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = GDScriptParser::get_builtin_type(first);
- if (script_class->identifier && script_class->identifier->name == first) {
- result = script_class->get_datatype();
- break;
+ if (result.builtin_type == Variant::ARRAY) {
+ GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
+ if (container_type.kind != GDScriptParser::DataType::VARIANT) {
+ container_type.is_constant = false;
+ result.set_container_element_type(container_type);
+ }
+ }
+ } else if (class_exists(first)) {
+ // Native engine classes.
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = first;
+ } else if (ScriptServer::is_global_class(first)) {
+ if (parser->script_path == ScriptServer::get_global_class_path(first)) {
+ result = parser->head->get_datatype();
+ } else {
+ String path = ScriptServer::get_global_class_path(first);
+ String ext = path.get_extension();
+ if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(path);
+ if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(ResourceLoader::load(path, "Script"));
+ }
+ }
+ } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
+ const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
+ Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
+ if (ref.is_null()) {
+ push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
+ return bad_type;
+ }
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
+ return bad_type;
}
- if (script_class->members_indices.has(first)) {
- resolve_class_member(script_class, first, p_type);
+ result = ref->get_parser()->head->get_datatype();
+ } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
+ // Native enum in current class.
+ result = make_native_enum_type(first, parser->current_class->base_type.native_type);
+ } else if (CoreConstants::is_global_enum(first)) {
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
+ return bad_type;
+ }
+ result = make_global_enum_type(first, StringName());
+ } else {
+ // Classes in current scope.
+ List<GDScriptParser::ClassNode *> script_classes;
+ bool found = false;
+ get_class_node_current_scope_classes(parser->current_class, &script_classes);
+ for (GDScriptParser::ClassNode *script_class : script_classes) {
+ if (found) {
+ break;
+ }
- GDScriptParser::ClassNode::Member member = script_class->get_member(first);
- switch (member.type) {
- case GDScriptParser::ClassNode::Member::CLASS:
- result = member.get_datatype();
- found = true;
- break;
- case GDScriptParser::ClassNode::Member::ENUM:
- result = member.get_datatype();
- found = true;
- break;
- case GDScriptParser::ClassNode::Member::CONSTANT:
- if (member.get_datatype().is_meta_type) {
+ if (script_class->identifier && script_class->identifier->name == first) {
+ result = script_class->get_datatype();
+ break;
+ }
+ if (script_class->members_indices.has(first)) {
+ resolve_class_member(script_class, first, p_type);
+
+ GDScriptParser::ClassNode::Member member = script_class->get_member(first);
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
result = member.get_datatype();
found = true;
break;
- } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
- Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
- if (gdscript.is_valid()) {
- Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
- if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type);
- return bad_type;
- }
- result = ref->get_parser()->head->get_datatype();
- } else {
- result = make_script_meta_type(member.constant->initializer->reduced_value);
- }
+ case GDScriptParser::ClassNode::Member::ENUM:
+ result = member.get_datatype();
found = true;
break;
- }
- [[fallthrough]];
- default:
- push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
- return bad_type;
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ if (member.get_datatype().is_meta_type) {
+ result = member.get_datatype();
+ found = true;
+ break;
+ } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
+ Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
+ if (gdscript.is_valid()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(member.constant->initializer->reduced_value);
+ }
+ found = true;
+ break;
+ }
+ [[fallthrough]];
+ default:
+ push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
+ return bad_type;
+ }
}
}
}
}
+
if (!result.is_set()) {
push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
return bad_type;
@@ -882,9 +920,12 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
case GDScriptParser::ClassNode::Member::VARIABLE: {
bool previous_static_context = static_context;
static_context = member.variable->is_static;
+
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
+
member.variable->set_datatype(resolving_datatype);
resolve_variable(member.variable, false);
+ resolve_pending_lambda_bodies();
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
@@ -893,7 +934,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
E->apply(parser, member.variable);
}
}
+
static_context = previous_static_context;
+
#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
@@ -956,10 +999,16 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
GDScriptParser::ParameterNode *param = member.signal->parameters[j];
GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier));
param->set_datatype(param_type);
- mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name));
- // TODO: add signal parameter default values
+#ifdef DEBUG_ENABLED
+ if (param->datatype_specifier == nullptr) {
+ parser->push_warning(param, GDScriptWarning::UNTYPED_DECLARATION, "Parameter", param->identifier->name);
+ }
+#endif
+ mi.arguments.push_back(param_type.to_property_info(param->identifier->name));
+ // Signals do not support parameter default values.
}
member.signal->set_datatype(make_signal_type(mi));
+ member.signal->method_info = mi;
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) {
@@ -1234,17 +1283,15 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
} else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) {
if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
if (member.variable->getter != nullptr) {
- member.variable->getter->set_datatype(member.variable->datatype);
+ member.variable->getter->return_type = member.variable->datatype_specifier;
+ member.variable->getter->set_datatype(member.get_datatype());
resolve_function_body(member.variable->getter);
}
if (member.variable->setter != nullptr) {
- resolve_function_signature(member.variable->setter);
-
- if (member.variable->setter->parameters.size() > 0) {
- member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier;
- member.variable->setter->parameters[0]->set_datatype(member.get_datatype());
- }
+ ERR_CONTINUE(member.variable->setter->parameters.is_empty());
+ member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier;
+ member.variable->setter->parameters[0]->set_datatype(member.get_datatype());
resolve_function_body(member.variable->setter);
}
@@ -1345,6 +1392,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
}
}
+ if (!pending_body_resolution_lambdas.is_empty()) {
+ ERR_PRINT("GDScript bug (please report): Not all pending lambda bodies were resolved in time.");
+ resolve_pending_lambda_bodies();
+ }
+
parser->current_class = previous_class;
}
@@ -1362,7 +1414,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo
}
void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) {
- ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node.");
+ ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node.");
switch (p_node->type) {
case GDScriptParser::Node::NONE:
@@ -1543,21 +1595,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
int default_value_count = 0;
#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
+ String function_visible_name = function_name;
+ if (function_name == StringName()) {
+ function_visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>";
+ }
+#endif
+
for (int i = 0; i < p_function->parameters.size(); i++) {
resolve_parameter(p_function->parameters[i]);
#ifdef DEBUG_ENABLED
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
- String visible_name = function_name;
- if (function_name == StringName()) {
- visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>";
- }
- parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name);
+ parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name);
}
is_shadowing(p_function->parameters[i]->identifier, "function parameter", true);
#endif // DEBUG_ENABLED
-#ifdef TOOLS_ENABLED
+
if (p_function->parameters[i]->initializer) {
+#ifdef TOOLS_ENABLED
default_value_count++;
+#endif // TOOLS_ENABLED
if (p_function->parameters[i]->initializer->is_constant) {
p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value);
@@ -1565,7 +1622,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
p_function->default_arg_values.push_back(Variant()); // Prevent shift.
}
}
-#endif // TOOLS_ENABLED
}
if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) {
@@ -1615,15 +1671,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
StringName native_base;
if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) {
bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC);
- valid = valid && parent_return_type == p_function->get_datatype();
+
+ if (p_function->return_type != nullptr) {
+ // Check return type covariance.
+ GDScriptParser::DataType return_type = p_function->get_datatype();
+ if (return_type.is_variant()) {
+ // `is_type_compatible()` returns `true` if one of the types is `Variant`.
+ // Don't allow an explicitly specified `Variant` if the parent return type is narrower.
+ valid = valid && parent_return_type.is_variant();
+ } else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
+ // `is_type_compatible()` returns `true` if target is an `Object` and source is `null`.
+ // Don't allow `void` if the parent return type is a hard non-`void` type.
+ if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) {
+ valid = false;
+ }
+ } else {
+ valid = valid && is_type_compatible(parent_return_type, return_type);
+ }
+ }
int par_count_diff = p_function->parameters.size() - parameters_types.size();
valid = valid && par_count_diff >= 0;
valid = valid && default_value_count >= default_par_count + par_count_diff;
- int i = 0;
- for (const GDScriptParser::DataType &par_type : parameters_types) {
- valid = valid && par_type == p_function->parameters[i++]->get_datatype();
+ if (valid) {
+ int i = 0;
+ for (const GDScriptParser::DataType &parent_par_type : parameters_types) {
+ // Check parameter type contravariance.
+ GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype();
+ if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) {
+ // `is_type_compatible()` returns `true` if one of the types is `Variant`.
+ // Don't allow narrowing a hard `Variant`.
+ valid = valid && current_par_type.is_variant();
+ } else {
+ valid = valid && is_type_compatible(current_par_type, parent_par_type);
+ }
+ }
}
if (!valid) {
@@ -1647,7 +1730,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
}
parent_signature += ") -> ";
- const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant";
+ const String return_type = parent_return_type.to_string_strict();
if (return_type == "null") {
parent_signature += "void";
} else {
@@ -1665,6 +1748,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
#endif // TOOLS_ENABLED
}
+#ifdef DEBUG_ENABLED
+ if (p_function->return_type == nullptr) {
+ parser->push_warning(p_function, GDScriptWarning::UNTYPED_DECLARATION, "Function", function_visible_name);
+ }
+#endif
+
if (p_function->get_datatype().is_resolving()) {
p_function->set_datatype(prev_datatype);
}
@@ -1757,6 +1846,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
#endif // DEBUG_ENABLED
resolve_node(stmt);
+ resolve_pending_lambda_bodies();
#ifdef DEBUG_ENABLED
parser->ignored_warnings = previously_ignored_warnings;
@@ -1867,6 +1957,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
}
}
+#ifdef DEBUG_ENABLED
+ if (!has_specified_type) {
+ const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER;
+ const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable");
+ if (p_assignable->infer_datatype || is_constant) {
+ parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name);
+ } else {
+ parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name);
+ }
+ }
+#endif
+
type.is_constant = is_constant;
type.is_read_only = false;
p_assignable->set_datatype(type);
@@ -2001,13 +2103,16 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
}
GDScriptParser::DataType variable_type;
+ String list_visible_type = "<unresolved type>";
if (list_resolved) {
variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
variable_type.kind = GDScriptParser::DataType::BUILTIN;
variable_type.builtin_type = Variant::INT;
+ list_visible_type = "Array[int]"; // NOTE: `range()` has `Array` return type.
} else if (p_for->list) {
resolve_node(p_for->list, false);
GDScriptParser::DataType list_type = p_for->list->get_datatype();
+ list_visible_type = list_type.to_string();
if (!list_type.is_hard_type()) {
mark_node_unsafe(p_for->list);
}
@@ -2051,8 +2156,39 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list);
}
}
+
if (p_for->variable) {
- p_for->variable->set_datatype(variable_type);
+ if (p_for->datatype_specifier) {
+ GDScriptParser::DataType specified_type = type_from_metatype(resolve_datatype(p_for->datatype_specifier));
+ if (!specified_type.is_variant()) {
+ if (variable_type.is_variant() || !variable_type.is_hard_type()) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else if (!is_type_compatible(specified_type, variable_type, true, p_for->variable)) {
+ if (is_type_compatible(variable_type, specified_type)) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else {
+ push_error(vformat(R"(Unable to iterate on value of type "%s" with variable of type "%s".)", list_visible_type, specified_type.to_string()), p_for->datatype_specifier);
+ }
+ } else if (!is_type_compatible(specified_type, variable_type)) {
+ p_for->use_conversion_assign = true;
+ }
+ if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ }
+ }
+ p_for->variable->set_datatype(specified_type);
+ } else {
+ p_for->variable->set_datatype(variable_type);
+#ifdef DEBUG_ENABLED
+ if (variable_type.is_hard_type()) {
+ parser->push_warning(p_for->variable, GDScriptWarning::INFERRED_DECLARATION, R"("for" iterator variable)", p_for->variable->name);
+ } else {
+ parser->push_warning(p_for->variable, GDScriptWarning::UNTYPED_DECLARATION, R"("for" iterator variable)", p_for->variable->name);
+ }
+#endif
+ }
}
resolve_suite(p_for->loop);
@@ -2108,6 +2244,10 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m
resolve_match_pattern(p_match_branch->patterns[i], p_match_test);
}
+ if (p_match_branch->guard_body) {
+ resolve_suite(p_match_branch->guard_body);
+ }
+
resolve_suite(p_match_branch->block);
decide_suite_type(p_match_branch, p_match_branch->block);
@@ -2441,28 +2581,31 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr
// When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed.
// 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.
+
for (int i = 0; i < p_array->elements.size(); i++) {
GDScriptParser::ExpressionNode *element_node = p_array->elements[i];
if (element_node->is_constant) {
- update_const_expression_builtin_type(element_node, p_element_type, "include");
+ update_const_expression_builtin_type(element_node, expected_type, "include");
}
- const GDScriptParser::DataType &element_type = element_node->get_datatype();
- if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) {
+ const GDScriptParser::DataType &actual_type = element_node->get_datatype();
+ if (actual_type.has_no_type() || actual_type.is_variant() || !actual_type.is_hard_type()) {
mark_node_unsafe(element_node);
continue;
}
- if (!is_type_compatible(p_element_type, element_type, true, p_array)) {
- if (is_type_compatible(element_type, p_element_type)) {
+ if (!is_type_compatible(expected_type, actual_type, true, p_array)) {
+ if (is_type_compatible(actual_type, expected_type)) {
mark_node_unsafe(element_node);
continue;
}
- push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node);
+ push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", actual_type.to_string(), expected_type.to_string()), element_node);
return;
}
}
GDScriptParser::DataType array_type = p_array->get_datatype();
- array_type.set_container_element_type(p_element_type);
+ array_type.set_container_element_type(expected_type);
p_array->set_datatype(array_type);
}
@@ -2509,7 +2652,7 @@ 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.has_container_element_type()) {
+ 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());
}
@@ -2788,19 +2931,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) {
// Call to name directly.
StringName function_name = p_call->function_name;
- Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
+ if (function_name == SNAME("Object")) {
+ push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call);
+ p_call->set_datatype(call_type);
+ return;
+ }
+
+ Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
if (builtin_type < Variant::VARIANT_MAX) {
// Is a builtin constructor.
call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
call_type.kind = GDScriptParser::DataType::BUILTIN;
call_type.builtin_type = builtin_type;
- if (builtin_type == Variant::OBJECT) {
- call_type.kind = GDScriptParser::DataType::NATIVE;
- call_type.native_type = function_name; // "Object".
- }
-
bool safe_to_fold = true;
switch (builtin_type) {
// Those are stored by reference so not suited for compile-time construction.
@@ -2836,7 +2980,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
- push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1,
+ push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1,
Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
break;
@@ -2852,10 +2996,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee);
} break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
- push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
+ push_error(vformat(R"*(Too many arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
break;
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
- push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
+ push_error(vformat(R"*(Too few arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
break;
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST:
@@ -2866,21 +3010,27 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
break;
}
} else {
- // TODO: Check constructors without constants.
-
// If there's one argument, try to use copy constructor (those aren't explicitly defined).
if (p_call->arguments.size() == 1) {
GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype();
- if (arg_type.is_variant()) {
- mark_node_unsafe(p_call->arguments[0]);
- } else {
+ if (arg_type.is_hard_type() && !arg_type.is_variant()) {
if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) {
// Okay.
p_call->set_datatype(call_type);
return;
}
+ } else {
+#ifdef DEBUG_ENABLED
+ mark_node_unsafe(p_call);
+ // We don't know what type was expected since constructors support overloads.
+ // TODO: Improve this by checking for matching candidates?
+ parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", function_name, "<unknown type>", "Variant");
+#endif
+ p_call->set_datatype(call_type);
+ return;
}
}
+
List<MethodInfo> constructors;
Variant::get_constructor_list(builtin_type, &constructors);
bool match = false;
@@ -2897,14 +3047,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
for (int i = 0; i < p_call->arguments.size(); i++) {
GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true);
-
- if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) {
+ GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
+ if (!is_type_compatible(par_type, arg_type, true)) {
types_match = false;
break;
#ifdef DEBUG_ENABLED
} else {
- if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) {
- parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
+ if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) {
+ parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name);
}
#endif
}
@@ -2912,9 +3062,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
if (types_match) {
for (int i = 0; i < p_call->arguments.size(); i++) {
+ GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true);
if (p_call->arguments[i]->is_constant) {
- update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass");
+ update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass");
+ }
+#ifdef DEBUG_ENABLED
+ if (!(par_type.is_variant() && par_type.is_hard_type())) {
+ GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
+ if (arg_type.is_variant() || !arg_type.is_hard_type() || !is_type_compatible(arg_type, par_type, true)) {
+ mark_node_unsafe(p_call);
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), function_name, par_type.to_string(), arg_type.to_string_strict());
+ }
}
+#endif
}
match = true;
call_type = type_from_property(info.return_val);
@@ -3048,7 +3208,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
base_type.is_meta_type = false;
is_self = true;
- if (p_call->callee == nullptr && !lambda_stack.is_empty()) {
+ if (p_call->callee == nullptr && current_lambda != nullptr) {
push_error("Cannot use `super()` inside a lambda.", p_call);
}
} else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
@@ -3103,11 +3263,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new");
+ if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) {
+ push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call);
+ p_call->set_datatype(call_type);
+ return;
+ }
+
if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) {
- // If the function require typed arrays we must make literals be typed.
+ // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there.
+ // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time.
+ if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) {
+ push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
+ }
+
+ // 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].has_container_element_type()) {
+ 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());
}
}
@@ -3208,8 +3380,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
#else
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
#endif // SUGGEST_GODOT4_RENAMES
- } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
- push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
+ } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) {
+ push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call);
}
}
@@ -3301,17 +3473,26 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) {
GDScriptParser::DataType result;
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::NATIVE;
- result.native_type = SNAME("Node");
- result.builtin_type = Variant::OBJECT;
+ result.kind = GDScriptParser::DataType::VARIANT;
- if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) {
- push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
+ if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") on a class that isn't a node.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
+ }
+
+ if (static_context) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") in a static function.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
}
mark_lambda_use_self();
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = SNAME("Node");
p_get_node->set_datatype(result);
}
@@ -3348,7 +3529,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide
p_identifier->set_datatype(p_identifier_datatype);
Error err = OK;
- Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err);
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier);
return;
@@ -3469,6 +3650,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) {
reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype());
+ if (script_class->outer != nullptr) {
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS;
+ }
return;
}
@@ -3685,6 +3869,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
#endif
// Not a local, so check members.
+
if (!found_source) {
reduce_identifier_from_base(p_identifier);
if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) {
@@ -3709,7 +3894,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
}
- if (!lambda_stack.is_empty()) {
+ if (current_lambda != nullptr) {
// If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance.
if (source_is_variable || source_is_signal) {
mark_lambda_use_self();
@@ -3721,7 +3906,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
return;
}
- GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function;
+ GDScriptParser::FunctionNode *function_test = current_lambda->function;
// Make sure we aren't capturing variable in the same lambda.
// This also add captures for nested lambdas.
while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) {
@@ -3737,10 +3922,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
StringName name = p_identifier->name;
p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE;
- // Check globals. We make an exception for Variant::OBJECT because it's the base class for
- // non-builtin types so we allow doing e.g. Object.new()
+ // Not a local or a member, so check globals.
+
Variant::Type builtin_type = GDScriptParser::get_builtin_type(name);
- if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) {
+ if (builtin_type < Variant::VARIANT_MAX) {
if (can_be_builtin) {
p_identifier->set_datatype(make_builtin_meta_type(builtin_type));
return;
@@ -3876,34 +4061,12 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) {
return;
}
- lambda_stack.push_back(p_lambda);
+ GDScriptParser::LambdaNode *previous_lambda = current_lambda;
+ current_lambda = p_lambda;
resolve_function_signature(p_lambda->function, p_lambda, true);
- resolve_function_body(p_lambda->function, true);
- lambda_stack.pop_back();
-
- int captures_amount = p_lambda->captures.size();
- if (captures_amount > 0) {
- // Create space for lambda parameters.
- // At the beginning to not mess with optional parameters.
- int param_count = p_lambda->function->parameters.size();
- p_lambda->function->parameters.resize(param_count + captures_amount);
- for (int i = param_count - 1; i >= 0; i--) {
- p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i];
- p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount;
- }
-
- // Add captures as extra parameters at the beginning.
- for (int i = 0; i < p_lambda->captures.size(); i++) {
- GDScriptParser::IdentifierNode *capture = p_lambda->captures[i];
- GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>();
- capture_param->identifier = capture;
- capture_param->usages = capture->usages;
- capture_param->set_datatype(capture->get_datatype());
+ current_lambda = previous_lambda;
- p_lambda->function->parameters.write[i] = capture_param;
- p_lambda->function->parameters_indices[capture->name] = i;
- }
- }
+ pending_body_resolution_lambdas.push_back(p_lambda);
}
void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
@@ -4003,7 +4166,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
bool valid = false;
// If the base is a metatype, use the analyzer instead.
- if (p_subscript->base->is_constant && !base_type.is_meta_type) {
+ if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) {
// Just try to get it.
Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
if (valid) {
@@ -4491,7 +4654,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
Ref<Script> script_type = p_element_datatype.script_type;
if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
- Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err);
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node);
return array;
@@ -4890,21 +5053,28 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
if (arg_type.is_variant() || !arg_type.is_hard_type()) {
+#ifdef DEBUG_ENABLED
// Argument can be anything, so this is unsafe (unless the parameter is a hard variant).
if (!(par_type.is_hard_type() && par_type.is_variant())) {
mark_node_unsafe(p_call->arguments[i]);
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
}
+#endif
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
- // Supertypes are acceptable for dynamic compliance, but it's unsafe.
- mark_node_unsafe(p_call);
if (!is_type_compatible(arg_type, par_type)) {
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*",
p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()),
p_call->arguments[i]);
+#ifdef DEBUG_ENABLED
+ } else {
+ // Supertypes are acceptable for dynamic compliance, but it's unsafe.
+ mark_node_unsafe(p_call);
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
+#endif
}
#ifdef DEBUG_ENABLED
} else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
- parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
#endif
}
}
@@ -4930,9 +5100,13 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function");
return;
} else if (ClassDB::class_exists(name)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class");
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "native class");
+ return;
+ } else if (ScriptServer::is_global_class(name)) {
+ String class_path = ScriptServer::get_global_class_path(name).get_file();
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path));
return;
- } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) {
+ } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type");
return;
}
@@ -5244,9 +5418,53 @@ void GDScriptAnalyzer::downgrade_node_type_source(GDScriptParser::Node *p_node)
}
void GDScriptAnalyzer::mark_lambda_use_self() {
- for (GDScriptParser::LambdaNode *lambda : lambda_stack) {
+ GDScriptParser::LambdaNode *lambda = current_lambda;
+ while (lambda != nullptr) {
lambda->use_self = true;
+ lambda = lambda->parent_lambda;
+ }
+}
+
+void GDScriptAnalyzer::resolve_pending_lambda_bodies() {
+ if (pending_body_resolution_lambdas.is_empty()) {
+ return;
+ }
+
+ GDScriptParser::LambdaNode *previous_lambda = current_lambda;
+
+ List<GDScriptParser::LambdaNode *> lambdas = pending_body_resolution_lambdas;
+ pending_body_resolution_lambdas.clear();
+
+ for (GDScriptParser::LambdaNode *lambda : lambdas) {
+ current_lambda = lambda;
+ resolve_function_body(lambda->function, true);
+
+ int captures_amount = lambda->captures.size();
+ if (captures_amount > 0) {
+ // Create space for lambda parameters.
+ // At the beginning to not mess with optional parameters.
+ int param_count = lambda->function->parameters.size();
+ lambda->function->parameters.resize(param_count + captures_amount);
+ for (int i = param_count - 1; i >= 0; i--) {
+ lambda->function->parameters.write[i + captures_amount] = lambda->function->parameters[i];
+ lambda->function->parameters_indices[lambda->function->parameters[i]->identifier->name] = i + captures_amount;
+ }
+
+ // Add captures as extra parameters at the beginning.
+ for (int i = 0; i < lambda->captures.size(); i++) {
+ GDScriptParser::IdentifierNode *capture = lambda->captures[i];
+ GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>();
+ capture_param->identifier = capture;
+ capture_param->usages = capture->usages;
+ capture_param->set_datatype(capture->get_datatype());
+
+ lambda->function->parameters.write[i] = capture_param;
+ lambda->function->parameters_indices[capture->name] = i;
+ }
+ }
}
+
+ current_lambda = previous_lambda;
}
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 5bc2c89a87..ec155706df 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -43,7 +43,8 @@ class GDScriptAnalyzer {
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
const GDScriptParser::EnumNode *current_enum = nullptr;
- List<GDScriptParser::LambdaNode *> lambda_stack;
+ GDScriptParser::LambdaNode *current_lambda = nullptr;
+ List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas;
bool static_context = false;
// Tests for detecting invalid overloading of script members
@@ -129,6 +130,7 @@ class GDScriptAnalyzer {
void mark_node_unsafe(const GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::Node *p_node);
void mark_lambda_use_self();
+ void resolve_pending_lambda_bodies();
bool class_exists(const StringName &p_class) const;
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 6057a00f9b..8394fce9b3 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -35,9 +35,6 @@
#include "core/debugger/engine_debugger.h"
uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) {
-#ifdef TOOLS_ENABLED
- function->arg_names.push_back(p_name);
-#endif
function->_argument_count++;
function->argument_types.push_back(p_type);
if (p_is_optional) {
@@ -1494,19 +1491,16 @@ void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_typ
for_container_variables.push_back(container);
}
-void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &p_list) {
+void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_list) {
const Address &container = for_container_variables.back()->get();
// Assign container.
append_opcode(GDScriptFunction::OPCODE_ASSIGN);
append(container);
append(p_list);
-
- for_iterator_variables.push_back(p_variable);
}
-void GDScriptByteCodeGenerator::write_for() {
- const Address &iterator = for_iterator_variables.back()->get();
+void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_conversion) {
const Address &counter = for_counter_variables.back()->get();
const Address &container = for_container_variables.back()->get();
@@ -1599,11 +1593,16 @@ void GDScriptByteCodeGenerator::write_for() {
}
}
+ Address temp;
+ if (p_use_conversion) {
+ temp = Address(Address::LOCAL_VARIABLE, add_local("@iterator_temp", GDScriptDataType()));
+ }
+
// Begin loop.
append_opcode(begin_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // End of loop address, will be patched.
append_opcode(GDScriptFunction::OPCODE_JUMP);
@@ -1615,9 +1614,17 @@ void GDScriptByteCodeGenerator::write_for() {
append_opcode(iterate_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // Jump destination, will be patched.
+
+ if (p_use_conversion) {
+ write_assign_with_conversion(p_variable, temp);
+ const GDScriptDataType &type = p_variable.type;
+ if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
+ write_assign_false(temp); // Can contain RefCounted, so clear it.
+ }
+ }
}
void GDScriptByteCodeGenerator::write_endfor() {
@@ -1639,7 +1646,6 @@ void GDScriptByteCodeGenerator::write_endfor() {
current_breaks_to_patch.pop_back();
// Pop state.
- for_iterator_variables.pop_back();
for_counter_variables.pop_back();
for_container_variables.pop_back();
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index bbcd252b13..671dea5d6d 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -143,7 +143,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
// Lists since these can be nested.
List<int> if_jmp_addrs;
List<int> for_jmp_addrs;
- List<Address> for_iterator_variables;
List<Address> for_counter_variables;
List<Address> for_container_variables;
List<int> while_jmp_addrs;
@@ -536,8 +535,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) override;
virtual void write_end_jump_if_shared() override;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override;
- virtual void write_for() override;
+ virtual void write_for_assignment(const Address &p_list) override;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) override;
virtual void write_endfor() override;
virtual void start_while_condition() override;
virtual void write_while(const Address &p_condition) override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index d191bd0224..18609d0b80 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -59,7 +59,7 @@ GDScriptAnalyzer *GDScriptParserRef::get_analyzer() {
}
Error GDScriptParserRef::raise_status(Status p_new_status) {
- ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA);
+ ERR_FAIL_NULL_V(parser, ERR_INVALID_DATA);
if (result != OK) {
return result;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 9810f5395a..cf17353dec 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -145,8 +145,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) = 0;
virtual void write_end_jump_if_shared() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0;
- virtual void write_for() = 0;
+ virtual void write_for_assignment(const Address &p_list) = 0;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) = 0;
virtual void write_endfor() = 0;
virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
virtual void write_while(const Address &p_condition) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 2a52db4158..f417d323db 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -60,7 +60,7 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa
scr = scr->_base;
}
- ERR_FAIL_COND_V(!nc, false);
+ ERR_FAIL_NULL_V(nc, false);
return ClassDB::has_property(nc->get_name(), p_name);
}
@@ -84,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
}
}
-GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) {
+GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype) {
if (!p_datatype.is_set() || !p_datatype.is_hard_type() || p_datatype.is_coroutine) {
return GDScriptDataType();
}
@@ -101,11 +101,36 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.builtin_type = p_datatype.builtin_type;
} break;
case GDScriptParser::DataType::NATIVE: {
+ if (p_handle_metatype && p_datatype.is_meta_type) {
+ result.kind = GDScriptDataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ // Fixes GH-82255. `GDScriptNativeClass` is obtainable in GDScript,
+ // but is not a registered and exposed class, so `GDScriptNativeClass`
+ // is missing from `GDScriptLanguage::get_singleton()->get_global_map()`.
+ //result.native_type = GDScriptNativeClass::get_class_static();
+ result.native_type = Object::get_class_static();
+ break;
+ }
+
result.kind = GDScriptDataType::NATIVE;
- result.native_type = p_datatype.native_type;
result.builtin_type = p_datatype.builtin_type;
+ result.native_type = p_datatype.native_type;
+
+#ifdef DEBUG_ENABLED
+ if (unlikely(!GDScriptLanguage::get_singleton()->get_global_map().has(result.native_type))) {
+ ERR_PRINT(vformat(R"(GDScript bug: Native class "%s" not found.)", result.native_type));
+ result.native_type = Object::get_class_static();
+ }
+#endif
} break;
case GDScriptParser::DataType::SCRIPT: {
+ if (p_handle_metatype && p_datatype.is_meta_type) {
+ result.kind = GDScriptDataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = p_datatype.script_type.is_valid() ? p_datatype.script_type->get_class() : Script::get_class_static();
+ break;
+ }
+
result.kind = GDScriptDataType::SCRIPT;
result.builtin_type = p_datatype.builtin_type;
result.script_type_ref = p_datatype.script_type;
@@ -113,6 +138,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.native_type = p_datatype.native_type;
} break;
case GDScriptParser::DataType::CLASS: {
+ if (p_handle_metatype && p_datatype.is_meta_type) {
+ result.kind = GDScriptDataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = GDScript::get_class_static();
+ break;
+ }
+
result.kind = GDScriptDataType::GDSCRIPT;
result.builtin_type = p_datatype.builtin_type;
result.native_type = p_datatype.native_type;
@@ -148,6 +180,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
} break;
case GDScriptParser::DataType::ENUM:
+ if (p_handle_metatype && p_datatype.is_meta_type) {
+ result.kind = GDScriptDataType::BUILTIN;
+ result.builtin_type = Variant::DICTIONARY;
+ break;
+ }
+
result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = p_datatype.builtin_type;
break;
@@ -159,7 +197,7 @@ 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));
+ result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner, false));
}
return result;
@@ -245,6 +283,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// MEMBERS.
case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
+ case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
+ case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: {
// Try class members.
if (_is_class_member_property(codegen, identifier)) {
@@ -271,45 +311,44 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
}
}
- } break;
- case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
- case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: {
- // Try methods and signals (can be Callable and Signal).
- // Search upwards through parent classes:
- const GDScriptParser::ClassNode *base_class = codegen.class_node;
- while (base_class != nullptr) {
- if (base_class->has_member(identifier)) {
- const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier);
- 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);
- return temp;
+ // Try methods and signals (can be Callable and Signal).
+ {
+ // Search upwards through parent classes:
+ const GDScriptParser::ClassNode *base_class = codegen.class_node;
+ while (base_class != nullptr) {
+ if (base_class->has_member(identifier)) {
+ const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier);
+ 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);
+ return temp;
+ }
}
+ base_class = base_class->base_type.class_type;
}
- base_class = base_class->base_type.class_type;
- }
- // Try in native base.
- GDScript *scr = codegen.script;
- GDScriptNativeClass *nc = nullptr;
- while (scr) {
- if (scr->native.is_valid()) {
- nc = scr->native.ptr();
+ // Try in native base.
+ GDScript *scr = codegen.script;
+ GDScriptNativeClass *nc = nullptr;
+ while (scr) {
+ if (scr->native.is_valid()) {
+ nc = scr->native.ptr();
+ }
+ scr = scr->_base;
}
- scr = scr->_base;
- }
- if (nc && (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);
+ if (nc && (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);
- gen->write_get_named(temp, identifier, self);
- return temp;
+ gen->write_get_named(temp, identifier, self);
+ return temp;
+ }
}
} break;
case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
@@ -319,6 +358,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
while (owner) {
GDScript *scr = owner;
GDScriptNativeClass *nc = nullptr;
+
while (scr) {
if (scr->constants.has(identifier)) {
return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here.
@@ -400,7 +440,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
String global_class_path = ScriptServer::get_global_class_path(identifier);
if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") {
Error err = OK;
- res = GDScriptCache::get_full_script(global_class_path, err);
+ // Should not need to pass p_owner since analyzer will already have done it.
+ res = GDScriptCache::get_shallow_script(global_class_path, err);
if (err != OK) {
_set_error("Can't load global class " + String(identifier), p_expression);
r_error = ERR_COMPILATION_FAILED;
@@ -531,7 +572,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
- GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script);
+ GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script, false);
GDScriptCodeGenerator::Address result;
if (cast_type.has_type) {
@@ -571,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
arguments.push_back(arg);
}
- if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) {
- // Construct a built-in type.
- Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
-
- gen->write_construct(result, vtype, arguments);
+ if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
+ gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) {
// Variant utility function.
gen->write_call_utility(result, call->function_name, arguments);
@@ -909,7 +947,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand);
- GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script);
+ GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script, false);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
@@ -1887,6 +1925,26 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
}
}
+ // If there's a guard, check its condition too.
+ if (branch->guard_body != nullptr) {
+ // Do this first so the guard does not run unless the pattern matched.
+ gen->write_and_left_operand(pattern_result);
+
+ // Don't actually use the block for the guard.
+ // The binds are already in the locals and we don't want to clear the result of the guard condition before we check the actual match.
+ GDScriptCodeGenerator::Address guard_result = _parse_expression(codegen, err, static_cast<GDScriptParser::ExpressionNode *>(branch->guard_body->statements[0]));
+ if (err) {
+ return err;
+ }
+
+ gen->write_and_right_operand(guard_result);
+ gen->write_end_and(pattern_result);
+
+ if (guard_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+ }
+
// Check if pattern did match.
gen->write_if(pattern_result);
@@ -1951,13 +2009,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
return err;
}
- gen->write_for_assignment(iterator, list);
+ gen->write_for_assignment(list);
if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- gen->write_for();
+ gen->write_for(iterator, for_n->use_conversion_assign);
err = _parse_block(codegen, for_n->loop);
if (err) {
@@ -2163,8 +2221,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
}
+ MethodInfo method_info;
+
codegen.function_name = func_name;
+ method_info.name = func_name;
codegen.is_static = is_static;
+ if (is_static) {
+ method_info.flags |= METHOD_FLAG_STATIC;
+ }
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
int optional_parameters = 0;
@@ -2176,10 +2240,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type);
codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type);
+ method_info.arguments.push_back(parameter->get_datatype().to_property_info(parameter->identifier->name));
+
if (parameter->initializer != nullptr) {
optional_parameters++;
}
}
+
+ method_info.default_arguments.append_array(p_func->default_arg_values);
}
// Parse initializer if applies.
@@ -2333,20 +2401,20 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
if (p_func) {
- // if no return statement -> return type is void not unresolved Variant
+ // If no `return` statement, then return type is `void`, not `Variant`.
if (p_func->body->has_return) {
gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
+ method_info.return_val = p_func->get_datatype().to_property_info(String());
} else {
gd_function->return_type = GDScriptDataType();
gd_function->return_type.has_type = true;
gd_function->return_type.kind = GDScriptDataType::BUILTIN;
gd_function->return_type.builtin_type = Variant::NIL;
}
-#ifdef TOOLS_ENABLED
- gd_function->default_arg_values = p_func->default_arg_values;
-#endif
}
+ gd_function->method_info = method_info;
+
if (!is_implicit_initializer && !is_implicit_ready && !p_for_lambda) {
p_script->member_functions[func_name] = gd_function;
}
@@ -2501,7 +2569,10 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return err;
}
-Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+// Prepares given script, and inner class scripts, for compilation. It populates class members and initializes method
+// RPC info for its base classes first, then for itself, then for inner classes.
+// Warning: this function cannot initiate compilation of other classes, or it will result in cyclic dependency issues.
+Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
if (parsed_classes.has(p_script)) {
return OK;
}
@@ -2552,7 +2623,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->member_functions.clear();
p_script->member_indices.clear();
- p_script->member_info.clear();
p_script->static_variables_indices.clear();
p_script->static_variables.clear();
p_script->_signals.clear();
@@ -2560,19 +2630,20 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->implicit_initializer = nullptr;
p_script->implicit_ready = nullptr;
p_script->static_initializer = nullptr;
+ p_script->rpc_config.clear();
p_script->clearing = false;
p_script->tool = parser->is_tool();
- if (!p_script->name.is_empty()) {
- if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) {
- _set_error("The class '" + p_script->name + "' shadows a native class", p_class);
+ if (p_script->local_name != StringName()) {
+ if (ClassDB::class_exists(p_script->local_name) && ClassDB::is_class_exposed(p_script->local_name)) {
+ _set_error(vformat(R"(The class "%s" shadows a native class)", p_script->local_name), p_class);
return ERR_ALREADY_EXISTS;
}
}
- GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script);
+ GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script, false);
int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type];
p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
@@ -2590,7 +2661,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
if (main_script->has_class(base.ptr())) {
- Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state);
+ Error err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state);
if (err) {
return err;
}
@@ -2609,7 +2680,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
return ERR_COMPILATION_FAILED;
}
- err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state);
+ err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state);
if (err) {
_set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr);
return err;
@@ -2626,6 +2697,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
} break;
}
+ // Duplicate RPC information from base GDScript
+ // Base script isn't valid because it should not have been compiled yet, but the reference contains relevant info.
+ if (base_type.kind == GDScriptDataType::GDSCRIPT && p_script->base.is_valid()) {
+ p_script->rpc_config = p_script->base->rpc_config.duplicate();
+ }
+
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = p_class->members[i];
switch (member.type) {
@@ -2634,7 +2711,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
StringName name = variable->identifier->name;
GDScript::MemberInfo minfo;
- minfo.index = p_script->member_indices.size();
switch (variable->property) {
case GDScriptParser::VariableNode::PROP_NONE:
break; // Nothing to do.
@@ -2657,8 +2733,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script);
- PropertyInfo prop_info = minfo.data_type;
- prop_info.name = name;
+ PropertyInfo prop_info = variable->get_datatype().to_property_info(name);
PropertyInfo export_info = variable->export_info;
if (variable->exported) {
@@ -2668,16 +2743,16 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
prop_info.hint = export_info.hint;
prop_info.hint_string = export_info.hint_string;
- prop_info.usage = export_info.usage | PROPERTY_USAGE_SCRIPT_VARIABLE;
- } else {
- prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
+ prop_info.usage = export_info.usage;
}
+ prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ minfo.property_info = prop_info;
if (variable->is_static) {
minfo.index = p_script->static_variables_indices.size();
p_script->static_variables_indices[name] = minfo;
} else {
- p_script->member_info[name] = prop_info;
+ minfo.index = p_script->member_indices.size();
p_script->member_indices[name] = minfo;
p_script->members.insert(name);
}
@@ -2710,12 +2785,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
const GDScriptParser::SignalNode *signal = member.signal;
StringName name = signal->identifier->name;
- Vector<StringName> parameters_names;
- parameters_names.resize(signal->parameters.size());
- for (int j = 0; j < signal->parameters.size(); j++) {
- parameters_names.write[j] = signal->parameters[j]->identifier->name;
- }
- p_script->_signals[name] = parameters_names;
+ p_script->_signals[name] = signal->method_info;
} break;
case GDScriptParser::ClassNode::Member::ENUM: {
@@ -2738,12 +2808,20 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
prop_info.name = annotation->export_info.name;
prop_info.usage = annotation->export_info.usage;
prop_info.hint_string = annotation->export_info.hint_string;
+ minfo.property_info = prop_info;
- p_script->member_info[name] = prop_info;
p_script->member_indices[name] = minfo;
- p_script->members.insert(Variant());
+ p_script->members.insert(name);
} break;
+ case GDScriptParser::ClassNode::Member::FUNCTION: {
+ const GDScriptParser::FunctionNode *function_n = member.function;
+
+ Variant config = function_n->rpc_config;
+ if (config.get_type() != Variant::NIL) {
+ p_script->rpc_config[function_n->identifier->name] = config;
+ }
+ } break;
default:
break; // Nothing to do here.
}
@@ -2754,7 +2832,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
parsed_classes.insert(p_script);
parsing_classes.erase(p_script);
- // Populate sub-classes.
+ // Populate inner classes.
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = p_class->members[i];
if (member.type != member.CLASS) {
@@ -2767,7 +2845,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
// Subclass might still be parsing, just skip it
if (!parsing_classes.has(subclass_ptr)) {
- Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state);
+ Error err = _prepare_compilation(subclass_ptr, inner_class, p_keep_state);
if (err) {
return err;
}
@@ -2903,8 +2981,6 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
has_static_data = has_static_data || inner_class->has_static_data;
}
- p_script->_init_rpc_methods_properties();
-
p_script->valid = true;
return OK;
}
@@ -2925,7 +3001,9 @@ void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDS
void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
p_script->fully_qualified_name = p_class->fqcn;
- p_script->name = p_class->identifier ? p_class->identifier->name : "";
+ p_script->local_name = p_class->identifier ? p_class->identifier->name : StringName();
+ p_script->global_name = p_class->get_global_name();
+ p_script->simplified_icon_path = p_class->simplified_icon_path;
HashMap<StringName, Ref<GDScript>> old_subclasses;
@@ -2976,7 +3054,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
make_scripts(p_script, root, p_keep_state);
main_script->_owner = nullptr;
- Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state);
+ Error err = _prepare_compilation(main_script, parser->get_tree(), p_keep_state);
if (err) {
return err;
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 2f522da4ea..099bd00a2e 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -124,7 +124,7 @@ class GDScriptCompiler {
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner);
+ GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true);
GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
@@ -135,7 +135,7 @@ class GDScriptCompiler {
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
- Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
int err_line = 0;
int err_column = 0;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index d27ea974e3..adfe4a3290 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -59,6 +59,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '");
p_delimiters->push_back("\"\"\" \"\"\"");
p_delimiters->push_back("''' '''");
+ // NOTE: StringName, NodePath and r-strings are not listed here.
}
bool GDScriptLanguage::is_using_templates() {
@@ -75,19 +76,25 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
#endif
if (!type_hints) {
processed_template = processed_template.replace(": int", "")
+ .replace(": Shader.Mode", "")
+ .replace(": VisualShader.Type", "")
+ .replace(": float", "")
.replace(": String", "")
.replace(": Array[String]", "")
- .replace(": float", "")
+ .replace(": Node", "")
.replace(": CharFXTransform", "")
.replace(":=", "=")
- .replace(" -> String", "")
- .replace(" -> int", "")
+ .replace(" -> void", "")
.replace(" -> bool", "")
- .replace(" -> void", "");
+ .replace(" -> int", "")
+ .replace(" -> PortType", "")
+ .replace(" -> String", "")
+ .replace(" -> Object", "");
}
processed_template = processed_template.replace("_BASE_", p_base_class_name)
- .replace("_CLASS_", p_class_name.to_pascal_case())
+ .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier())
+ .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier())
.replace("_TS_", _get_indentation());
scr->set_source_code(processed_template);
return scr;
@@ -190,10 +197,6 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li
return true;
}
-bool GDScriptLanguage::has_named_classes() const {
- return false;
-}
-
bool GDScriptLanguage::supports_builtin_mode() const {
return true;
}
@@ -500,7 +503,7 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
s += p_args[i].get_slice(":", 0);
if (th) {
String type = p_args[i].get_slice(":", 1);
- if (!type.is_empty() && type != "var") {
+ if (!type.is_empty()) {
s += ": " + type;
}
}
@@ -1076,7 +1079,7 @@ 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.class_name);
+ int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
r_result.insert(option.display, option);
}
@@ -1150,7 +1153,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
if (E.name.contains("/")) {
continue;
}
- int location = p_recursion_depth + _get_property_location(type, E.class_name);
+ int location = p_recursion_depth + _get_property_location(type, E.name);
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
r_result.insert(option.display, option);
}
@@ -1207,6 +1210,9 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
for (const PropertyInfo &E : members) {
if (!String(E.name).contains("/")) {
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER);
+ if (GDScriptParser::theme_color_names.has(E.name)) {
+ option.theme_color_name = GDScriptParser::theme_color_names[E.name];
+ }
r_result.insert(option.display, option);
}
}
@@ -1456,8 +1462,13 @@ 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);
- if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) {
- r_type.type = p_expression->get_datatype();
+ switch (p_expression->get_datatype().kind) {
+ case GDScriptParser::DataType::ENUM:
+ case GDScriptParser::DataType::CLASS:
+ r_type.type = p_expression->get_datatype();
+ break;
+ default:
+ break;
}
found = true;
} else {
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index a6b4dc7981..372c212d2b 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -32,14 +32,6 @@
#include "gdscript.h"
-const int *GDScriptFunction::get_code() const {
- return _code_ptr;
-}
-
-int GDScriptFunction::get_code_size() const {
- return _code_size;
-}
-
Variant GDScriptFunction::get_constant(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>");
return constants[p_idx];
@@ -50,32 +42,6 @@ StringName GDScriptFunction::get_global_name(int p_idx) const {
return global_names[p_idx];
}
-int GDScriptFunction::get_default_argument_count() const {
- return _default_arg_count;
-}
-
-int GDScriptFunction::get_default_argument_addr(int p_idx) const {
- ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), -1);
- return default_arguments[p_idx];
-}
-
-GDScriptDataType GDScriptFunction::get_return_type() const {
- return return_type;
-}
-
-GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const {
- ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType());
- return argument_types[p_idx];
-}
-
-StringName GDScriptFunction::get_name() const {
- return name;
-}
-
-int GDScriptFunction::get_max_stack_size() const {
- return _stack_size;
-}
-
struct _GDFKC {
int order = 0;
List<int> pos;
@@ -161,9 +127,7 @@ GDScriptFunction::~GDScriptFunction() {
return_type.script_type_ref = Ref<Script>();
#ifdef DEBUG_ENABLED
-
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
-
GDScriptLanguage::get_singleton()->function_list.remove(&function_list);
#endif
}
@@ -176,7 +140,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar
if (p_argcount == 0) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = 1;
+ r_error.expected = 1;
return Variant();
} else if (p_argcount == 1) {
//noooneee
@@ -224,7 +188,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
}
Variant GDScriptFunctionState::resume(const Variant &p_arg) {
- ERR_FAIL_COND_V(!function, Variant());
+ ERR_FAIL_NULL_V(function, Variant());
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 5230773c13..e984d97149 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -147,39 +147,12 @@ public:
return false;
}
- operator PropertyInfo() const {
- PropertyInfo info;
- info.usage = PROPERTY_USAGE_NONE;
- if (has_type) {
- switch (kind) {
- case UNINITIALIZED:
- break;
- case BUILTIN: {
- info.type = builtin_type;
- } break;
- case NATIVE: {
- info.type = Variant::OBJECT;
- info.class_name = native_type;
- } break;
- case SCRIPT:
- case GDSCRIPT: {
- info.type = Variant::OBJECT;
- info.class_name = script_type->get_instance_base_type();
- } break;
- }
- } else {
- info.type = Variant::NIL;
- info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- }
- return info;
- }
-
void set_container_element_type(const GDScriptDataType &p_element_type) {
container_element_type = memnew(GDScriptDataType(p_element_type));
}
GDScriptDataType get_container_element_type() const {
- ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType());
+ ERR_FAIL_NULL_V(container_element_type, GDScriptDataType());
return *container_element_type;
}
@@ -437,59 +410,32 @@ private:
friend class GDScript;
friend class GDScriptCompiler;
friend class GDScriptByteCodeGenerator;
+ friend class GDScriptLanguage;
+ StringName name;
StringName source;
+ bool _static = false;
+ Vector<GDScriptDataType> argument_types;
+ GDScriptDataType return_type;
+ MethodInfo method_info;
+ Variant rpc_config;
- mutable Variant nil;
- mutable Variant *_constants_ptr = nullptr;
- int _constant_count = 0;
- const StringName *_global_names_ptr = nullptr;
- int _global_names_count = 0;
- const int *_default_arg_ptr = nullptr;
- int _default_arg_count = 0;
- int _operator_funcs_count = 0;
- const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr;
- int _setters_count = 0;
- const Variant::ValidatedSetter *_setters_ptr = nullptr;
- int _getters_count = 0;
- const Variant::ValidatedGetter *_getters_ptr = nullptr;
- int _keyed_setters_count = 0;
- const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr;
- int _keyed_getters_count = 0;
- const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr;
- int _indexed_setters_count = 0;
- const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr;
- int _indexed_getters_count = 0;
- const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr;
- int _builtin_methods_count = 0;
- const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr;
- int _constructors_count = 0;
- const Variant::ValidatedConstructor *_constructors_ptr = nullptr;
- int _utilities_count = 0;
- const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr;
- int _gds_utilities_count = 0;
- const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr;
- int _methods_count = 0;
- MethodBind **_methods_ptr = nullptr;
- int _lambdas_count = 0;
- GDScriptFunction **_lambdas_ptr = nullptr;
- int *_code_ptr = nullptr;
- int _code_size = 0;
+ GDScript *_script = nullptr;
+ int _initial_line = 0;
int _argument_count = 0;
int _stack_size = 0;
int _instruction_args_size = 0;
int _ptrcall_args_size = 0;
- int _initial_line = 0;
- bool _static = false;
- Variant rpc_config;
-
- GDScript *_script = nullptr;
+ SelfList<GDScriptFunction> function_list{ this };
+ mutable Variant nil;
+ HashMap<int, Variant::Type> temporary_slots;
+ List<StackDebug> stack_debug;
- StringName name;
+ Vector<int> code;
+ Vector<int> default_arguments;
Vector<Variant> constants;
Vector<StringName> global_names;
- Vector<int> default_arguments;
Vector<Variant::ValidatedOperatorEvaluator> operator_funcs;
Vector<Variant::ValidatedSetter> setters;
Vector<Variant::ValidatedGetter> getters;
@@ -503,18 +449,47 @@ private:
Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities;
Vector<MethodBind *> methods;
Vector<GDScriptFunction *> lambdas;
- Vector<int> code;
- Vector<GDScriptDataType> argument_types;
- GDScriptDataType return_type;
- HashMap<int, Variant::Type> temporary_slots;
+ int _code_size = 0;
+ int _default_arg_count = 0;
+ int _constant_count = 0;
+ int _global_names_count = 0;
+ int _operator_funcs_count = 0;
+ int _setters_count = 0;
+ int _getters_count = 0;
+ int _keyed_setters_count = 0;
+ int _keyed_getters_count = 0;
+ int _indexed_setters_count = 0;
+ int _indexed_getters_count = 0;
+ int _builtin_methods_count = 0;
+ int _constructors_count = 0;
+ int _utilities_count = 0;
+ int _gds_utilities_count = 0;
+ int _methods_count = 0;
+ int _lambdas_count = 0;
-#ifdef TOOLS_ENABLED
- Vector<StringName> arg_names;
- Vector<Variant> default_arg_values;
-#endif
+ int *_code_ptr = nullptr;
+ const int *_default_arg_ptr = nullptr;
+ mutable Variant *_constants_ptr = nullptr;
+ const StringName *_global_names_ptr = nullptr;
+ const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr;
+ const Variant::ValidatedSetter *_setters_ptr = nullptr;
+ const Variant::ValidatedGetter *_getters_ptr = nullptr;
+ const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr;
+ const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr;
+ const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr;
+ const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr;
+ const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr;
+ const Variant::ValidatedConstructor *_constructors_ptr = nullptr;
+ const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr;
+ const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr;
+ MethodBind **_methods_ptr = nullptr;
+ GDScriptFunction **_lambdas_ptr = nullptr;
#ifdef DEBUG_ENABLED
+ CharString func_cname;
+ const char *_func_cname = nullptr;
+
Vector<String> operator_names;
Vector<String> setter_names;
Vector<String> getter_names;
@@ -522,20 +497,6 @@ private:
Vector<String> constructors_names;
Vector<String> utilities_names;
Vector<String> gds_utilities_names;
-#endif
-
- List<StackDebug> stack_debug;
-
- Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
-
- _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const;
-
- friend class GDScriptLanguage;
-
- SelfList<GDScriptFunction> function_list{ this };
-#ifdef DEBUG_ENABLED
- CharString func_cname;
- const char *_func_cname = nullptr;
struct Profile {
StringName signature;
@@ -549,9 +510,11 @@ private:
uint64_t last_frame_self_time = 0;
uint64_t last_frame_total_time = 0;
} profile;
-
#endif
+ _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const;
+ Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
+
public:
static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow.
@@ -571,51 +534,24 @@ public:
Variant result;
};
+ _FORCE_INLINE_ StringName get_name() const { return name; }
+ _FORCE_INLINE_ StringName get_source() const { return source; }
+ _FORCE_INLINE_ GDScript *get_script() const { return _script; }
_FORCE_INLINE_ bool is_static() const { return _static; }
+ _FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; }
+ _FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; }
+ _FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; }
- const int *get_code() const; //used for debug
- int get_code_size() const;
Variant get_constant(int p_idx) const;
StringName get_global_name(int p_idx) const;
- StringName get_name() const;
- int get_max_stack_size() const;
- int get_default_argument_count() const;
- int get_default_argument_addr(int p_idx) const;
- GDScriptDataType get_return_type() const;
- GDScriptDataType get_argument_type(int p_idx) const;
- GDScript *get_script() const { return _script; }
- StringName get_source() const { return source; }
-
- void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const;
-
- _FORCE_INLINE_ bool is_empty() const { return _code_size == 0; }
-
- int get_argument_count() const { return _argument_count; }
- StringName get_argument_name(int p_idx) const {
-#ifdef TOOLS_ENABLED
- ERR_FAIL_INDEX_V(p_idx, arg_names.size(), StringName());
- return arg_names[p_idx];
-#else
- return StringName();
-#endif
- }
- Variant get_default_argument(int p_idx) const {
- ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant());
- return default_arguments[p_idx];
- }
-#ifdef TOOLS_ENABLED
- const Vector<Variant> &get_default_arg_values() const {
- return default_arg_values;
- }
-#endif // TOOLS_ENABLED
Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr);
+ void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const;
#ifdef DEBUG_ENABLED
void disassemble(const Vector<String> &p_code_lines) const;
#endif
- _FORCE_INLINE_ const Variant get_rpc_config() const { return rpc_config; }
GDScriptFunction();
~GDScriptFunction();
};
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 9e14e43a58..9d0fce0928 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -67,6 +67,10 @@ ObjectID GDScriptLambdaCallable::get_object() const {
return script->get_instance_id();
}
+StringName GDScriptLambdaCallable::get_method() const {
+ return function->get_name();
+}
+
void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
int captures_amount = captures.size();
@@ -75,13 +79,48 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
args.resize(p_argcount + captures_amount);
for (int i = 0; i < captures_amount; i++) {
args.write[i] = &captures[i];
+ if (captures[i].get_type() == Variant::OBJECT) {
+ bool was_freed = false;
+ captures[i].get_validated_object_with_check(was_freed);
+ if (was_freed) {
+ ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
+ static Variant nil;
+ args.write[i] = &nil;
+ }
+ }
}
for (int i = 0; i < p_argcount; i++) {
args.write[i + captures_amount] = p_arguments[i];
}
r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error);
- r_call_error.argument -= captures_amount;
+ switch (r_call_error.error) {
+ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
+ r_call_error.argument -= captures_amount;
+#ifdef DEBUG_ENABLED
+ if (r_call_error.argument < 0) {
+ ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument));
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ }
+#endif
+ break;
+ case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
+ case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
+ r_call_error.expected -= captures_amount;
+#ifdef DEBUG_ENABLED
+ if (r_call_error.expected < 0) {
+ ERR_PRINT("GDScript bug (please report): Invalid lambda captures count.");
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ }
+#endif
+ break;
+ default:
+ break;
+ }
} else {
r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error);
}
@@ -144,13 +183,48 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
args.resize(p_argcount + captures_amount);
for (int i = 0; i < captures_amount; i++) {
args.write[i] = &captures[i];
+ if (captures[i].get_type() == Variant::OBJECT) {
+ bool was_freed = false;
+ captures[i].get_validated_object_with_check(was_freed);
+ if (was_freed) {
+ ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
+ static Variant nil;
+ args.write[i] = &nil;
+ }
+ }
}
for (int i = 0; i < p_argcount; i++) {
args.write[i + captures_amount] = p_arguments[i];
}
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error);
- r_call_error.argument -= captures_amount;
+ switch (r_call_error.error) {
+ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
+ r_call_error.argument -= captures_amount;
+#ifdef DEBUG_ENABLED
+ if (r_call_error.argument < 0) {
+ ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument));
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ }
+#endif
+ break;
+ case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
+ case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
+ r_call_error.expected -= captures_amount;
+#ifdef DEBUG_ENABLED
+ if (r_call_error.expected < 0) {
+ ERR_PRINT("GDScript bug (please report): Invalid lambda captures count.");
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ }
+#endif
+ break;
+ default:
+ break;
+ }
} else {
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), p_arguments, p_argcount, r_call_error);
}
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index 33bdf6dfc1..1c7a18fb9d 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -56,6 +56,7 @@ public:
CompareEqualFunc get_compare_equal_func() const override;
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
+ 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(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index debc85ebbf..9fb1030d12 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -52,11 +52,18 @@
#include "editor/editor_settings.h"
#endif
+// This function is used to determine that a type is "built-in" as opposed to native
+// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded:
+// `Variant::NIL` - `null` is literal, not a type.
+// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type.
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
- if (builtin_types.is_empty()) {
- for (int i = 1; i < Variant::VARIANT_MAX; i++) {
- builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
+ if (unlikely(builtin_types.is_empty())) {
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ Variant::Type type = (Variant::Type)i;
+ if (type != Variant::NIL && type != Variant::OBJECT) {
+ builtin_types[Variant::get_type_name(type)] = type;
+ }
}
}
@@ -66,6 +73,10 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
return Variant::VARIANT_MAX;
}
+#ifdef TOOLS_ENABLED
+HashMap<String, String> GDScriptParser::theme_color_names;
+#endif
+
void GDScriptParser::cleanup() {
builtin_types.clear();
}
@@ -121,6 +132,15 @@ GDScriptParser::GDScriptParser() {
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
#endif
+
+#ifdef TOOLS_ENABLED
+ if (theme_color_names.is_empty()) {
+ theme_color_names.insert("x", "axis_x_color");
+ theme_color_names.insert("y", "axis_y_color");
+ theme_color_names.insert("z", "axis_z_color");
+ theme_color_names.insert("w", "axis_w_color");
+ }
+#endif
}
GDScriptParser::~GDScriptParser() {
@@ -157,7 +177,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
#ifdef DEBUG_ENABLED
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
- ERR_FAIL_COND(p_source == nullptr);
+ ERR_FAIL_NULL(p_source);
if (is_ignoring_warnings) {
return;
}
@@ -370,8 +390,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
push_error(current.literal);
current = tokenizer.scan();
}
- for (Node *n : nodes_in_progress) {
- update_extents(n);
+ if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
+ for (Node *n : nodes_in_progress) {
+ update_extents(n);
+ }
}
return previous;
}
@@ -566,13 +588,14 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) {
- if (E.value.new_line && E.value.comment.begins_with("##")) {
- class_doc_line = MIN(class_doc_line, E.key);
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ int line = MIN(max_script_doc_line, head->end_line);
+ while (line > 0) {
+ if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
+ head->doc_data = parse_class_doc_comment(line);
+ break;
}
- }
- if (has_comment(class_doc_line, true)) {
- head->doc_data = parse_class_doc_comment(class_doc_line, false);
+ line--;
}
#endif // TOOLS_ENABLED
@@ -734,10 +757,6 @@ template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
-#ifdef TOOLS_ENABLED
- int doc_comment_line = previous.start_line - 1;
-#endif // TOOLS_ENABLED
-
// Consume annotations.
List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
@@ -749,11 +768,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
}
-#ifdef TOOLS_ENABLED
- if (last_annotation->start_line == doc_comment_line) {
- doc_comment_line--;
- }
-#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)(p_is_static);
@@ -761,28 +775,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
return;
}
+#ifdef TOOLS_ENABLED
+ int doc_comment_line = member->start_line - 1;
+#endif // TOOLS_ENABLED
+
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
+#ifdef TOOLS_ENABLED
+ if (annotation->start_line <= doc_comment_line) {
+ doc_comment_line = annotation->start_line - 1;
+ }
+#endif // TOOLS_ENABLED
}
#ifdef TOOLS_ENABLED
- // Consume doc comments.
- class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
-
- // Check whether current line has a doc comment
- if (has_comment(previous.start_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(previous.start_line, true, true);
- } else {
- member->doc_data = parse_doc_comment(previous.start_line, true);
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_class_doc_comment(member->start_line, true);
+ } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
+ // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
+ member->doc_data = parse_class_doc_comment(doc_comment_line);
}
- } else if (has_comment(doc_comment_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(doc_comment_line, true);
- } else {
+ } else {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_doc_comment(member->start_line, true);
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
member->doc_data = parse_doc_comment(doc_comment_line);
}
}
+
+ min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment.
#endif // TOOLS_ENABLED
if (member->identifier != nullptr) {
@@ -1250,6 +1276,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
+#ifdef TOOLS_ENABLED
+ int min_enum_value_doc_line = previous.end_line + 1;
+#endif
HashMap<StringName, int> elements;
@@ -1312,43 +1341,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
}
} while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
- consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
-
#ifdef TOOLS_ENABLED
// Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
- int doc_comment_line = enum_node->values[i].line;
- bool single_line = false;
-
- if (has_comment(doc_comment_line, true)) {
- single_line = true;
- } else if (has_comment(doc_comment_line - 1, true)) {
- doc_comment_line--;
- } else {
- continue;
- }
-
- if (i == enum_node->values.size() - 1) {
- // If close bracket is same line as last value.
- if (doc_comment_line == previous.start_line) {
- break;
- }
- } else {
- // If two values are same line.
- if (doc_comment_line == enum_node->values[i + 1].line) {
- continue;
+ int enum_value_line = enum_node->values[i].line;
+ int doc_comment_line = enum_value_line - 1;
+
+ MemberDocData doc_data;
+ if (has_comment(enum_value_line, true)) {
+ // Inline doc comment.
+ if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
+ doc_data = parse_doc_comment(enum_value_line, true);
}
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
+ doc_data = parse_doc_comment(doc_comment_line);
}
if (named) {
- enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line);
+ enum_node->values.write[i].doc_data = doc_data;
} else {
- current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line));
+ current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data);
}
+
+ min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment.
}
#endif // TOOLS_ENABLED
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
complete_extents(enum_node);
end_statement("enum");
@@ -1850,7 +1871,18 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->variable = parse_identifier();
}
- consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ n_for->datatype_specifier = parse_type();
+ if (n_for->datatype_specifier == nullptr) {
+ push_error(R"(Expected type specifier after ":".)");
+ }
+ }
+
+ if (n_for->datatype_specifier == nullptr) {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" or ":" after "for" variable name.)");
+ } else {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable type specifier.)");
+ }
n_for->list = parse_expression(false);
@@ -2010,7 +2042,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
push_error(R"(No pattern found for "match" branch.)");
}
- if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) {
+ bool has_guard = false;
+ if (match(GDScriptTokenizer::Token::WHEN)) {
+ // Pattern guard.
+ // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope.
+ branch->guard_body = alloc_node<SuiteNode>();
+ if (branch->patterns.size() > 0) {
+ for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {
+ SuiteNode::Local local(E.value, current_function);
+ local.type = SuiteNode::Local::PATTERN_BIND;
+ branch->guard_body->add_local(local);
+ }
+ }
+
+ SuiteNode *parent_block = current_suite;
+ branch->guard_body->parent_block = parent_block;
+ current_suite = branch->guard_body;
+
+ ExpressionNode *guard = parse_expression(false);
+ if (guard == nullptr) {
+ push_error(R"(Expected expression for pattern guard after "when".)");
+ } else {
+ branch->guard_body->statements.append(guard);
+ }
+ current_suite = parent_block;
+ complete_extents(branch->guard_body);
+
+ has_guard = true;
+ branch->has_wildcard = false; // If it has a guard, the wildcard might still not match.
+ }
+
+ if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) {
complete_extents(branch);
return nullptr;
}
@@ -2280,9 +2342,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
-#ifdef DEBUG_ENABLED
identifier->suite = current_suite;
-#endif
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
@@ -2446,7 +2506,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
complete_extents(operation);
if (operation->right_operand == nullptr) {
- push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
+ push_error(vformat(R"(Expected expression after "%s" operator.)", op.get_name()));
}
// TODO: Also for unary, ternary, and assignment.
@@ -3035,10 +3095,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
if (previous.type == GDScriptTokenizer::Token::DOLLAR) {
// Detect initial slash, which will be handled in the loop if it matches.
match(GDScriptTokenizer::Token::SLASH);
-#ifdef DEBUG_ENABLED
} else {
get_node->use_dollar = false;
-#endif
}
int context_argument = 0;
@@ -3142,6 +3200,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
LambdaNode *lambda = alloc_node<LambdaNode>();
lambda->parent_function = current_function;
+ lambda->parent_lambda = current_lambda;
+
FunctionNode *function = alloc_node<FunctionNode>();
function->source_lambda = lambda;
@@ -3169,6 +3229,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
FunctionNode *previous_function = current_function;
current_function = function;
+ LambdaNode *previous_lambda = current_lambda;
+ current_lambda = lambda;
+
SuiteNode *body = alloc_node<SuiteNode>();
body->parent_function = current_function;
body->parent_block = current_suite;
@@ -3206,6 +3269,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
}
current_function = previous_function;
+ current_lambda = previous_lambda;
in_lambda = previous_in_lambda;
lambda->function = function;
@@ -3428,31 +3492,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
}
GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
- MemberDocData result;
+ ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.description = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
+ line--;
}
- line--;
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3462,11 +3516,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ MemberDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3487,35 +3540,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
return result;
}
-GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) {
- ClassDocData result;
+GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
+ ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.brief = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- bool is_in_brief = true;
- if (p_inner_class) {
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
- }
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
line--;
}
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3525,11 +3565,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ bool is_in_brief = true;
+ ClassDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3604,14 +3644,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
}
}
- if (current_class->members.size() > 0) {
- const ClassNode::Member &m = current_class->members[0];
- int first_member_line = m.get_line();
- if (first_member_line == line) {
- result = ClassDocData(); // Clear result.
- }
- }
-
return result;
}
#endif // TOOLS_ENABLED
@@ -3679,6 +3711,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // PASS,
{ nullptr, nullptr, PREC_NONE }, // RETURN,
{ nullptr, nullptr, PREC_NONE }, // MATCH,
+ { nullptr, nullptr, PREC_NONE }, // WHEN,
// Keywords
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
@@ -3827,18 +3860,31 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
+
ClassNode *p_class = static_cast<ClassNode *>(p_node);
+ String path = p_annotation->resolved_arguments[0];
+
#ifdef DEBUG_ENABLED
if (!p_class->icon_path.is_empty()) {
push_error(R"("@icon" annotation can only be used once.)", p_annotation);
return false;
}
- if (String(p_annotation->resolved_arguments[0]).is_empty()) {
+ if (path.is_empty()) {
push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]);
return false;
}
#endif // DEBUG_ENABLED
- p_class->icon_path = p_annotation->resolved_arguments[0];
+
+ p_class->icon_path = path;
+
+ if (path.is_empty() || path.is_absolute_path()) {
+ p_class->simplified_icon_path = path.simplify_path();
+ } else if (path.is_relative_path()) {
+ p_class->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path();
+ } else {
+ p_class->simplified_icon_path = path;
+ }
+
return true;
}
@@ -4056,23 +4102,29 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
} break;
case GDScriptParser::DataType::ENUM: {
- variable->export_info.type = Variant::INT;
- variable->export_info.hint = PROPERTY_HINT_ENUM;
-
- String enum_hint_string;
- bool first = true;
- for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
- if (!first) {
- enum_hint_string += ",";
- } else {
- first = false;
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
}
- enum_hint_string += E.key.operator String().capitalize().xml_escape();
- enum_hint_string += ":";
- enum_hint_string += String::num_int64(E.value).xml_escape();
- }
- variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
} break;
default:
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
@@ -4274,7 +4326,7 @@ String GDScriptParser::SuiteNode::Local::get_name() const {
case SuiteNode::Local::FOR_VARIABLE:
return "for loop iterator";
case SuiteNode::Local::PATTERN_BIND:
- return "pattern_bind";
+ return "pattern bind";
case SuiteNode::Local::UNDEFINED:
return "<undefined>";
default:
@@ -4331,6 +4383,104 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range.");
}
+PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const {
+ PropertyInfo result;
+ result.name = p_name;
+ result.usage = PROPERTY_USAGE_NONE;
+
+ if (!is_hard_type()) {
+ result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ return result;
+ }
+
+ 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) {
+ case BUILTIN:
+ result.hint = PROPERTY_HINT_ARRAY_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;
+ 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();
+ } else {
+ 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();
+ } else {
+ 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("::", ".");
+ break;
+ case VARIANT:
+ case RESOLVING:
+ case UNRESOLVED:
+ break;
+ }
+ }
+ break;
+ case NATIVE:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = GDScriptNativeClass::get_class_static();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case SCRIPT:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static();
+ } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) {
+ result.class_name = script_type->get_global_name();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case CLASS:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = GDScript::get_class_static();
+ } else if (class_type != nullptr && class_type->get_global_name() != StringName()) {
+ result.class_name = class_type->get_global_name();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case ENUM:
+ if (is_meta_type) {
+ result.type = Variant::DICTIONARY;
+ } else {
+ result.type = Variant::INT;
+ result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ result.class_name = String(native_type).replace("::", ".");
+ }
+ break;
+ case VARIANT:
+ case RESOLVING:
+ case UNRESOLVED:
+ result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ break;
+ }
+
+ return result;
+}
+
static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) {
switch (p_type) {
case Variant::PACKED_BYTE_ARRAY:
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 20f5dcf06d..2daadd7e6a 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -147,14 +147,17 @@ public:
_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
_FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == RESOLVING || kind == UNRESOLVED; }
_FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; }
+
String to_string() const;
+ _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_ DataType get_container_element_type() const {
- ERR_FAIL_COND_V(container_element_type == nullptr, DataType());
+ ERR_FAIL_NULL_V(container_element_type, DataType());
return *container_element_type;
}
@@ -724,6 +727,7 @@ public:
IdentifierNode *identifier = nullptr;
String icon_path;
+ String simplified_icon_path;
Vector<Member> members;
HashMap<StringName, int> members_indices;
ClassNode *outer = nullptr;
@@ -748,6 +752,10 @@ public:
bool resolved_interface = false;
bool resolved_body = false;
+ StringName get_global_name() const {
+ return (outer == nullptr && identifier != nullptr) ? identifier->name : StringName();
+ }
+
Member get_member(const StringName &p_name) const {
return members[members_indices[p_name]];
}
@@ -814,6 +822,8 @@ public:
struct ForNode : public Node {
IdentifierNode *variable = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool use_conversion_assign = false;
ExpressionNode *list = nullptr;
SuiteNode *loop = nullptr;
@@ -833,8 +843,8 @@ public:
Variant rpc_config;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
-#ifdef TOOLS_ENABLED
Vector<Variant> default_arg_values;
+#ifdef TOOLS_ENABLED
MemberDocData doc_data;
#endif // TOOLS_ENABLED
@@ -848,9 +858,7 @@ public:
struct GetNodeNode : public ExpressionNode {
String full_path;
-#ifdef DEBUG_ENABLED
bool use_dollar = true;
-#endif
GetNodeNode() {
type = GET_NODE;
@@ -859,9 +867,7 @@ public:
struct IdentifierNode : public ExpressionNode {
StringName name;
-#ifdef DEBUG_ENABLED
SuiteNode *suite = nullptr; // The block in which the identifier is used.
-#endif
enum Source {
UNDEFINED_SOURCE,
@@ -908,6 +914,7 @@ public:
struct LambdaNode : public ExpressionNode {
FunctionNode *function = nullptr;
FunctionNode *parent_function = nullptr;
+ LambdaNode *parent_lambda = nullptr;
Vector<IdentifierNode *> captures;
HashMap<StringName, int> captures_indices;
bool use_self = false;
@@ -942,6 +949,7 @@ public:
Vector<PatternNode *> patterns;
SuiteNode *block = nullptr;
bool has_wildcard = false;
+ SuiteNode *guard_body = nullptr;
MatchBranchNode() {
type = MATCH_BRANCH;
@@ -1026,6 +1034,7 @@ public:
IdentifierNode *identifier = nullptr;
Vector<ParameterNode *> parameters;
HashMap<StringName, int> parameters_indices;
+ MethodInfo method_info;
#ifdef TOOLS_ENABLED
MemberDocData doc_data;
#endif // TOOLS_ENABLED
@@ -1321,6 +1330,7 @@ private:
ClassNode *current_class = nullptr;
FunctionNode *current_function = nullptr;
+ LambdaNode *current_lambda = nullptr;
SuiteNode *current_suite = nullptr;
CompletionContext completion_context;
@@ -1509,10 +1519,11 @@ private:
TypeNode *parse_type(bool p_allow_void = false);
#ifdef TOOLS_ENABLED
- int class_doc_line = 0x7FFFFFFF;
+ int max_script_doc_line = INT_MAX;
+ int min_member_doc_line = 1;
bool has_comment(int p_line, bool p_must_be_doc = false);
MemberDocData parse_doc_comment(int p_line, bool p_single_line = false);
- ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false);
+ ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false);
#endif // TOOLS_ENABLED
public:
@@ -1521,7 +1532,7 @@ public:
bool is_tool() const { return _is_tool; }
ClassNode *find_class(const String &p_qualified_name) const;
bool has_class(const GDScriptParser::ClassNode *p_class) const;
- static Variant::Type get_builtin_type(const StringName &p_type);
+ static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`.
CompletionContext get_completion_context() const { return completion_context; }
CompletionCall get_completion_call() const { return completion_call; }
@@ -1539,6 +1550,10 @@ public:
int get_last_line_number() const { return current.end_line; }
#endif
+#ifdef TOOLS_ENABLED
+ static HashMap<String, String> theme_color_names;
+#endif // TOOLS_ENABLED
+
GDScriptParser();
~GDScriptParser();
diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp
index a4dd8a8d3c..a6d2388a91 100644
--- a/modules/gdscript/gdscript_rpc_callable.cpp
+++ b/modules/gdscript/gdscript_rpc_callable.cpp
@@ -30,6 +30,7 @@
#include "gdscript_rpc_callable.h"
+#include "core/object/script_language.h"
#include "core/templates/hashfuncs.h"
#include "scene/main/node.h"
@@ -63,6 +64,10 @@ ObjectID GDScriptRPCCallable::get_object() const {
return object->get_instance_id();
}
+StringName GDScriptRPCCallable::get_method() const {
+ return method;
+}
+
void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error);
}
@@ -73,7 +78,7 @@ GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_m
h = method.hash();
h = hash_murmur3_one_64(object->get_instance_id(), h);
node = Object::cast_to<Node>(object);
- ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node.");
+ ERR_FAIL_NULL_MSG(node, "RPC can only be defined on class that extends Node.");
}
Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h
index c1007b18b0..66052157be 100644
--- a/modules/gdscript/gdscript_rpc_callable.h
+++ b/modules/gdscript/gdscript_rpc_callable.h
@@ -51,6 +51,7 @@ public:
CompareEqualFunc get_compare_equal_func() const override;
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
+ 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;
Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 4f374b63b0..98a3a1268f 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -99,6 +99,7 @@ static const char *token_names[] = {
"pass", // PASS,
"return", // RETURN,
"match", // MATCH,
+ "when", // WHEN,
// Keywords
"as", // AS,
"assert", // ASSERT,
@@ -187,6 +188,7 @@ bool GDScriptTokenizer::Token::is_identifier() const {
switch (type) {
case IDENTIFIER:
case MATCH: // Used in String.match().
+ case WHEN: // New keyword, avoid breaking existing code.
// Allow constants to be treated as regular identifiers.
case CONST_PI:
case CONST_INF:
@@ -241,6 +243,7 @@ bool GDScriptTokenizer::Token::is_node_name() const {
case VAR:
case VOID:
case WHILE:
+ case WHEN:
case YIELD:
return true;
default:
@@ -531,6 +534,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
KEYWORD("void", Token::VOID) \
KEYWORD_GROUP('w') \
KEYWORD("while", Token::WHILE) \
+ KEYWORD("when", Token::WHEN) \
KEYWORD_GROUP('y') \
KEYWORD("yield", Token::YIELD) \
KEYWORD_GROUP('I') \
@@ -857,10 +861,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
STRING_NODEPATH,
};
+ bool is_raw = false;
bool is_multiline = false;
StringType type = STRING_REGULAR;
- if (_peek(-1) == '&') {
+ if (_peek(-1) == 'r') {
+ is_raw = true;
+ _advance();
+ } else if (_peek(-1) == '&') {
type = STRING_NAME;
_advance();
} else if (_peek(-1) == '^') {
@@ -890,7 +898,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
char32_t ch = _peek();
if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) {
- Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ Token error;
+ if (is_raw) {
+ error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string.");
+ } else {
+ error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ }
error.start_column = column;
error.leftmost_column = error.start_column;
error.end_column = column + 1;
@@ -905,144 +918,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_error("Unterminated string.");
}
- // Grab escape character.
- char32_t code = _peek();
- _advance();
- if (_is_at_end()) {
- return make_error("Unterminated string.");
- }
+ if (is_raw) {
+ if (_peek() == quote_char) {
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += quote_char;
+ } else if (_peek() == '\\') { // For `\\\"`.
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += '\\';
+ } else {
+ result += '\\';
+ }
+ } else {
+ // Grab escape character.
+ char32_t code = _peek();
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- char32_t escaped = 0;
- bool valid_escape = true;
+ char32_t escaped = 0;
+ bool valid_escape = true;
- switch (code) {
- case 'a':
- escaped = '\a';
- break;
- case 'b':
- escaped = '\b';
- break;
- case 'f':
- escaped = '\f';
- break;
- case 'n':
- escaped = '\n';
- break;
- case 'r':
- escaped = '\r';
- break;
- case 't':
- escaped = '\t';
- break;
- case 'v':
- escaped = '\v';
- break;
- case '\'':
- escaped = '\'';
- break;
- case '\"':
- escaped = '\"';
- break;
- case '\\':
- escaped = '\\';
- break;
- case 'U':
- case 'u': {
- // Hexadecimal sequence.
- int hex_len = (code == 'U') ? 6 : 4;
- for (int j = 0; j < hex_len; j++) {
- if (_is_at_end()) {
- return make_error("Unterminated string.");
+ switch (code) {
+ case 'a':
+ escaped = '\a';
+ break;
+ case 'b':
+ escaped = '\b';
+ break;
+ case 'f':
+ escaped = '\f';
+ break;
+ case 'n':
+ escaped = '\n';
+ break;
+ case 'r':
+ escaped = '\r';
+ break;
+ case 't':
+ escaped = '\t';
+ break;
+ case 'v':
+ escaped = '\v';
+ break;
+ case '\'':
+ escaped = '\'';
+ break;
+ case '\"':
+ escaped = '\"';
+ break;
+ case '\\':
+ escaped = '\\';
+ break;
+ case 'U':
+ case 'u': {
+ // Hexadecimal sequence.
+ int hex_len = (code == 'U') ? 6 : 4;
+ for (int j = 0; j < hex_len; j++) {
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+
+ char32_t digit = _peek();
+ char32_t value = 0;
+ if (is_digit(digit)) {
+ value = digit - '0';
+ } else if (digit >= 'a' && digit <= 'f') {
+ value = digit - 'a';
+ value += 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ value = digit - 'A';
+ value += 10;
+ } else {
+ // Make error, but keep parsing the string.
+ Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
+ error.start_column = column;
+ error.leftmost_column = error.start_column;
+ error.end_column = column + 1;
+ error.rightmost_column = error.end_column;
+ push_error(error);
+ valid_escape = false;
+ break;
+ }
+
+ escaped <<= 4;
+ escaped |= value;
+
+ _advance();
}
-
- char32_t digit = _peek();
- char32_t value = 0;
- if (is_digit(digit)) {
- value = digit - '0';
- } else if (digit >= 'a' && digit <= 'f') {
- value = digit - 'a';
- value += 10;
- } else if (digit >= 'A' && digit <= 'F') {
- value = digit - 'A';
- value += 10;
- } else {
- // Make error, but keep parsing the string.
- Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
- error.start_column = column;
- error.leftmost_column = error.start_column;
- error.end_column = column + 1;
- error.rightmost_column = error.end_column;
- push_error(error);
- valid_escape = false;
+ } break;
+ case '\r':
+ if (_peek() != '\n') {
+ // Carriage return without newline in string. (???)
+ // Just add it to the string and keep going.
+ result += ch;
+ _advance();
break;
}
-
- escaped <<= 4;
- escaped |= value;
-
- _advance();
- }
- } break;
- case '\r':
- if (_peek() != '\n') {
- // Carriage return without newline in string. (???)
- // Just add it to the string and keep going.
- result += ch;
- _advance();
+ [[fallthrough]];
+ case '\n':
+ // Escaping newline.
+ newline(false);
+ valid_escape = false; // Don't add to the string.
break;
- }
- [[fallthrough]];
- case '\n':
- // Escaping newline.
- newline(false);
- valid_escape = false; // Don't add to the string.
- break;
- default:
- Token error = make_error("Invalid escape in string.");
- error.start_column = column - 2;
- error.leftmost_column = error.start_column;
- push_error(error);
- valid_escape = false;
- break;
- }
- // Parse UTF-16 pair.
- if (valid_escape) {
- if ((escaped & 0xfffffc00) == 0xd800) {
- if (prev == 0) {
- prev = escaped;
- prev_pos = column - 2;
- continue;
- } else {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
+ default:
+ Token error = make_error("Invalid escape in string.");
error.start_column = column - 2;
error.leftmost_column = error.start_column;
push_error(error);
valid_escape = false;
- prev = 0;
+ break;
+ }
+ // Parse UTF-16 pair.
+ if (valid_escape) {
+ if ((escaped & 0xfffffc00) == 0xd800) {
+ if (prev == 0) {
+ prev = escaped;
+ prev_pos = column - 2;
+ continue;
+ } else {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ prev = 0;
+ }
+ } else if ((escaped & 0xfffffc00) == 0xdc00) {
+ if (prev == 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ } else {
+ escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
+ prev = 0;
+ }
}
- } else if ((escaped & 0xfffffc00) == 0xdc00) {
- if (prev == 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate");
- error.start_column = column - 2;
+ if (prev != 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = prev_pos;
error.leftmost_column = error.start_column;
push_error(error);
- valid_escape = false;
- } else {
- escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
prev = 0;
}
}
- if (prev != 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
- error.start_column = prev_pos;
- error.leftmost_column = error.start_column;
- push_error(error);
- prev = 0;
- }
- }
- if (valid_escape) {
- result += escaped;
+ if (valid_escape) {
+ result += escaped;
+ }
}
} else if (ch == quote_char) {
if (prev != 0) {
@@ -1162,15 +1195,6 @@ void GDScriptTokenizer::check_indent() {
_advance();
}
- if (mixed && !(line_continuation || multiline_mode)) {
- Token error = make_error("Mixed use of tabs and spaces for indentation.");
- error.start_line = line;
- error.start_column = 1;
- error.leftmost_column = 1;
- error.rightmost_column = column;
- push_error(error);
- }
-
if (_is_at_end()) {
// Reached the end with an empty line, so just dedent as much as needed.
pending_indents -= indent_level();
@@ -1214,9 +1238,18 @@ void GDScriptTokenizer::check_indent() {
continue;
}
+ if (mixed && !line_continuation && !multiline_mode) {
+ Token error = make_error("Mixed use of tabs and spaces for indentation.");
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.rightmost_column = column;
+ push_error(error);
+ }
+
if (line_continuation || multiline_mode) {
// We cleared up all the whitespace at the beginning of the line.
- // But if this is a continuation or multiline mode and we don't want any indentation change.
+ // If this is a line continuation or we're in multiline mode then we don't want any indentation changes.
return;
}
@@ -1416,6 +1449,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (is_digit(c)) {
return number();
+ } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) {
+ // Raw string literals.
+ return string();
} else if (is_unicode_identifier_start(c)) {
return potential_identifier();
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 068393cee9..6dd8a98652 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -105,6 +105,7 @@ public:
PASS,
RETURN,
MATCH,
+ WHEN,
// Keywords
AS,
ASSERT,
@@ -187,6 +188,8 @@ public:
#ifdef TOOLS_ENABLED
struct CommentData {
String comment;
+ // true: Comment starts at beginning of line or after indentation.
+ // false: Inline comment (starts after some code).
bool new_line = false;
CommentData() {}
CommentData(const String &p_comment, bool p_new_line) {
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 030950267d..69a0b42d89 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -45,14 +45,12 @@
#define VALIDATE_ARG_COUNT(m_count) \
if (p_arg_count < m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
- r_error.argument = m_count; \
r_error.expected = m_count; \
*r_ret = Variant(); \
return; \
} \
if (p_arg_count > m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
- r_error.argument = m_count; \
r_error.expected = m_count; \
*r_ret = Variant(); \
return; \
@@ -85,6 +83,7 @@
#endif
struct GDScriptUtilityFunctionsDefinitions {
+#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_INT(1);
@@ -100,6 +99,7 @@ struct GDScriptUtilityFunctionsDefinitions {
Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
}
+#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
@@ -117,7 +117,6 @@ struct GDScriptUtilityFunctionsDefinitions {
switch (p_arg_count) {
case 0: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = 1;
r_error.expected = 1;
*r_ret = Variant();
} break;
@@ -221,7 +220,6 @@ struct GDScriptUtilityFunctionsDefinitions {
} break;
default: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = 3;
r_error.expected = 3;
*r_ret = Variant();
@@ -249,6 +247,7 @@ struct GDScriptUtilityFunctionsDefinitions {
} else if (p_args[0]->get_type() != Variant::OBJECT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
+ r_error.expected = Variant::OBJECT;
*r_ret = Variant();
} else {
Object *obj = *p_args[0];
@@ -277,7 +276,7 @@ struct GDScriptUtilityFunctionsDefinitions {
Vector<StringName> sname;
while (p->_owner) {
- sname.push_back(p->name);
+ sname.push_back(p->local_name);
p = p->_owner;
}
sname.reverse();
@@ -388,13 +387,13 @@ struct GDScriptUtilityFunctionsDefinitions {
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (p_arg_count < 3) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = 3;
+ r_error.expected = 3;
*r_ret = Variant();
return;
}
if (p_arg_count > 4) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = 4;
+ r_error.expected = 4;
*r_ret = Variant();
return;
}
@@ -703,7 +702,9 @@ static void _register_function(const String &p_name, const MethodInfo &p_method_
PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
void GDScriptUtilityFunctions::register_functions() {
+#ifndef DISABLE_DEPRECATED
REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
+#endif // DISABLE_DEPRECATED
REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
@@ -725,50 +726,50 @@ void GDScriptUtilityFunctions::unregister_functions() {
GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, nullptr);
+ ERR_FAIL_NULL_V(info, nullptr);
return info->function;
}
bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, false);
+ ERR_FAIL_NULL_V(info, false);
return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
}
Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, Variant::NIL);
+ ERR_FAIL_NULL_V(info, Variant::NIL);
return info->info.return_val.type;
}
StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, StringName());
+ ERR_FAIL_NULL_V(info, StringName());
return info->info.return_val.class_name;
}
Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, Variant::NIL);
+ ERR_FAIL_NULL_V(info, Variant::NIL);
ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL);
return info->info.arguments[p_arg].type;
}
int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, 0);
+ ERR_FAIL_NULL_V(info, 0);
return info->info.arguments.size();
}
bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, false);
+ ERR_FAIL_NULL_V(info, false);
return (bool)(info->info.flags & METHOD_FLAG_VARARG);
}
bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, false);
+ ERR_FAIL_NULL_V(info, false);
return info->is_constant;
}
@@ -784,6 +785,6 @@ void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions)
MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
- ERR_FAIL_COND_V(!info, MethodInfo());
+ ERR_FAIL_NULL_V(info, MethodInfo());
return info->info;
}
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 1ddd54b323..5ecae08f6c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -116,6 +116,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
int errorarg = p_err.argument;
+ ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer.");
// Handle the Object to Object case separately as we don't have further class details.
#ifdef DEBUG_ENABLED
if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
@@ -128,9 +129,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
}
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
} else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
err_text = "Invalid call. Nonexistent " + p_where + ".";
} else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
@@ -397,7 +398,13 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+#ifdef _MSC_VER
+#define OPCODE_SWITCH(m_test) \
+ __assume(m_test <= OPCODE_END); \
+ switch (m_test)
+#else
#define OPCODE_SWITCH(m_test) switch (m_test)
+#endif
#define OPCODE_BREAK break
#define OPCODE_OUT break
#endif
@@ -466,8 +473,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_file = "<built-in>";
}
String err_func = name;
- if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) {
- err_func = p_instance->script->name + "." + err_func;
+ if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->local_name != StringName()) {
+ err_func = p_instance->script->local_name.operator String() + "." + err_func;
}
int err_line = _initial_line;
const char *err_text = "Stack overflow. Check for infinite recursion in your script.";
@@ -511,13 +518,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (p_argcount != _argument_count) {
if (p_argcount > _argument_count) {
r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_err.argument = _argument_count;
-
+ r_err.expected = _argument_count;
call_depth--;
return _get_default_variant_for_data_type(return_type);
} else if (p_argcount < _argument_count - _default_arg_count) {
r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_err.argument = _argument_count - _default_arg_count;
+ r_err.expected = _argument_count - _default_arg_count;
call_depth--;
return _get_default_variant_for_data_type(return_type);
} else {
@@ -3649,8 +3655,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_file = "<built-in>";
}
String err_func = name;
- if (instance_valid_with_script && !p_instance->script->name.is_empty()) {
- err_func = p_instance->script->name + "." + err_func;
+ if (instance_valid_with_script && p_instance->script->local_name != StringName()) {
+ err_func = p_instance->script->local_name.operator String() + "." + err_func;
}
int err_line = line;
if (err_text.is_empty()) {
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 24aa793c47..cabac07ef9 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -88,6 +88,15 @@ String GDScriptWarning::get_message() const {
case FUNCTION_USED_AS_PROPERTY:
CHECK_SYMBOLS(2);
return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)", symbols[0], symbols[1]);
+ case UNTYPED_DECLARATION:
+ CHECK_SYMBOLS(2);
+ if (symbols[0] == "Function") {
+ return vformat(R"*(%s "%s()" has no static return type.)*", symbols[0], symbols[1]);
+ }
+ return vformat(R"(%s "%s" has no static type.)", symbols[0], symbols[1]);
+ case INFERRED_DECLARATION:
+ CHECK_SYMBOLS(2);
+ return vformat(R"(%s "%s" has an implicitly inferred static type.)", symbols[0], symbols[1]);
case UNSAFE_PROPERTY_ACCESS:
CHECK_SYMBOLS(2);
return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]);
@@ -99,7 +108,7 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]);
case UNSAFE_CALL_ARGUMENT:
CHECK_SYMBOLS(4);
- return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]);
+ return vformat(R"*(The argument %s of the function "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]);
case UNSAFE_VOID_RETURN:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]);
@@ -200,6 +209,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"PROPERTY_USED_AS_FUNCTION",
"CONSTANT_USED_AS_FUNCTION",
"FUNCTION_USED_AS_PROPERTY",
+ "UNTYPED_DECLARATION",
+ "INFERRED_DECLARATION",
"UNSAFE_PROPERTY_ACCESS",
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 8444d46a88..1aef6fa81b 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -64,6 +64,8 @@ public:
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name.
+ UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred.
+ INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type.
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
UNSAFE_CAST, // Cast used in an unknown type.
@@ -111,6 +113,8 @@ public:
WARN, // PROPERTY_USED_AS_FUNCTION
WARN, // CONSTANT_USED_AS_FUNCTION
WARN, // FUNCTION_USED_AS_PROPERTY
+ IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings.
+ IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings.
IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios.
IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios.
IGNORE, // UNSAFE_CAST // Too common in untyped scenarios.
diff --git a/modules/gdscript/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg
index aa59125ea9..2671c007f3 100644
--- a/modules/gdscript/icons/GDScript.svg
+++ b/modules/gdscript/icons/GDScript.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="#e0e0e0"/></svg>
diff --git a/modules/gdscript/icons/GDScriptInternal.svg b/modules/gdscript/icons/GDScriptInternal.svg
index fcabaafbd0..81a59a7387 100644
--- a/modules/gdscript/icons/GDScriptInternal.svg
+++ b/modules/gdscript/icons/GDScriptInternal.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578c-.2364329.0758517-.4668872.16921-.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941c-.1119126.2211335-.2072287.4502818-.28516.68555l-2.2539.5625v2l2.2578.56445c.075942.2357685.1692993.465568.2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953c.2211335.111913.4502818.207229.68555.28516l.5625 2.2539h2l.56445-2.2578c.2357685-.07594.465568-.169299.6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941c.111913-.221133.207229-.4502818.28516-.68555l2.2539-.5625v-2l-2.2578-.56445c-.075942-.2357685-.169299-.4655679-.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953c-.221133-.1119126-.4502818-.2072287-.68555-.28516l-.5625-2.2539zm1 5c1.1045695 0 2 .8954305 2 2s-.8954305 2-2 2-2-.8954305-2-2 .8954305-2 2-2z" fill="none" stroke="#e0e0e0"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="none" stroke="#e0e0e0"/></svg>
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 3a5a54e275..36806d2f73 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -32,9 +32,94 @@
#include "../gdscript.h"
#include "../gdscript_analyzer.h"
+#include "editor/editor_settings.h"
#include "gdscript_language_protocol.h"
#include "gdscript_workspace.h"
+int get_indent_size() {
+ if (EditorSettings::get_singleton()) {
+ return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
+ } else {
+ return 4;
+ }
+}
+
+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) {
+ return res;
+ }
+ // Special case: `line = p_lines.size() + 1` -> root class (range covers everything).
+ if (this->line >= p_lines.size() + 1) {
+ res.line = p_lines.size();
+ return res;
+ }
+ res.line = this->line - 1;
+ // Note: character outside of `pos_line.length()-1` is valid.
+ res.character = this->column - 1;
+
+ String pos_line = p_lines[res.line];
+ if (pos_line.contains("\t")) {
+ int tab_size = get_indent_size();
+
+ int in_col = 1;
+ int res_char = 0;
+
+ while (res_char < pos_line.size() && in_col < this->column) {
+ if (pos_line[res_char] == '\t') {
+ in_col += tab_size;
+ res_char++;
+ } else {
+ in_col++;
+ res_char++;
+ }
+ }
+
+ res.character = res_char;
+ }
+
+ return res;
+}
+
+GodotPosition GodotPosition::from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines) {
+ GodotPosition res(p_pos.line + 1, p_pos.character + 1);
+
+ // Line outside of actual text is valid (-> pos/cursor at end of text).
+ if (res.line > p_lines.size()) {
+ return res;
+ }
+
+ String line = p_lines[p_pos.line];
+ int tabs_before_char = 0;
+ for (int i = 0; i < p_pos.character && i < line.length(); i++) {
+ if (line[i] == '\t') {
+ tabs_before_char++;
+ }
+ }
+
+ if (tabs_before_char > 0) {
+ int tab_size = get_indent_size();
+ res.column += tabs_before_char * (tab_size - 1);
+ }
+
+ return res;
+}
+
+lsp::Range GodotRange::to_lsp(const Vector<String> &p_lines) const {
+ lsp::Range res;
+ res.start = start.to_lsp(p_lines);
+ res.end = end.to_lsp(p_lines);
+ return res;
+}
+
+GodotRange GodotRange::from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines) {
+ GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines);
+ GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines);
+ return GodotRange(start, end);
+}
+
void ExtendGDScriptParser::update_diagnostics() {
diagnostics.clear();
@@ -90,7 +175,7 @@ void ExtendGDScriptParser::update_symbols() {
const lsp::DocumentSymbol &symbol = class_symbol.children[i];
members.insert(symbol.name, &symbol);
- // cache level one inner classes
+ // Cache level one inner classes.
if (symbol.kind == lsp::SymbolKind::Class) {
ClassMembers inner_class;
for (int j = 0; j < symbol.children.size(); j++) {
@@ -126,10 +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.start.line = LINE_NUMBER_TO_INDEX(token.start_line);
- link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line);
- link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column);
- link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column);
+ link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(this->lines);
document_links.push_back(link);
}
}
@@ -137,6 +219,12 @@ 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);
+}
+
void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
const String uri = get_uri();
@@ -149,13 +237,30 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
}
r_symbol.kind = lsp::SymbolKind::Class;
r_symbol.deprecated = false;
- r_symbol.range.start.line = p_class->start_line;
- r_symbol.range.start.character = p_class->start_column;
- r_symbol.range.end.line = lines.size();
- r_symbol.selectionRange.start.line = r_symbol.range.start.line;
+ r_symbol.range = range_of_node(p_class);
+ r_symbol.range.start.line = MAX(r_symbol.range.start.line, 0);
+ if (p_class->identifier) {
+ r_symbol.selectionRange = range_of_node(p_class->identifier);
+ }
r_symbol.detail = "class " + r_symbol.name;
- bool is_root_class = &r_symbol == &class_symbol;
- r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class);
+ {
+ String doc = p_class->doc_data.description;
+ if (!p_class->doc_data.description.is_empty()) {
+ doc += "\n\n" + p_class->doc_data.description;
+ }
+
+ if (!p_class->doc_data.tutorials.is_empty()) {
+ doc += "\n";
+ for (const Pair<String, String> &tutorial : p_class->doc_data.tutorials) {
+ if (tutorial.first.is_empty()) {
+ doc += vformat("\n@tutorial: %s", tutorial.second);
+ } else {
+ doc += vformat("\n@tutorial(%s): %s", tutorial.first, tutorial.second);
+ }
+ }
+ }
+ r_symbol.documentation = doc;
+ }
for (int i = 0; i < p_class->members.size(); i++) {
const ClassNode::Member &m = p_class->members[i];
@@ -166,11 +271,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.name = m.variable->identifier->name;
symbol.kind = m.variable->property == VariableNode::PROP_NONE ? lsp::SymbolKind::Variable : lsp::SymbolKind::Property;
symbol.deprecated = false;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column);
- symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.range = range_of_node(m.variable);
+ symbol.selectionRange = range_of_node(m.variable->identifier);
if (m.variable->exported) {
symbol.detail += "@export ";
}
@@ -182,10 +284,31 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string();
}
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line));
+ symbol.documentation = m.variable->doc_data.description;
symbol.uri = uri;
symbol.script_path = path;
+ if (m.variable->initializer && m.variable->initializer->type == GDScriptParser::Node::LAMBDA) {
+ GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)m.variable->initializer;
+ lsp::DocumentSymbol lambda;
+ parse_function_symbol(lambda_node->function, lambda);
+ // Merge lambda into current variable.
+ symbol.children.append_array(lambda.children);
+ }
+
+ if (m.variable->getter && m.variable->getter->type == GDScriptParser::Node::FUNCTION) {
+ lsp::DocumentSymbol get_symbol;
+ parse_function_symbol(m.variable->getter, get_symbol);
+ get_symbol.local = true;
+ symbol.children.push_back(get_symbol);
+ }
+ if (m.variable->setter && m.variable->setter->type == GDScriptParser::Node::FUNCTION) {
+ lsp::DocumentSymbol set_symbol;
+ parse_function_symbol(m.variable->setter, set_symbol);
+ set_symbol.local = true;
+ symbol.children.push_back(set_symbol);
+ }
+
r_symbol.children.push_back(symbol);
} break;
case ClassNode::Member::CONSTANT: {
@@ -194,12 +317,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.name = m.constant->identifier->name;
symbol.kind = lsp::SymbolKind::Constant;
symbol.deprecated = false;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
- symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line));
+ symbol.range = range_of_node(m.constant);
+ symbol.selectionRange = range_of_node(m.constant->identifier);
+ symbol.documentation = m.constant->doc_data.description;
symbol.uri = uri;
symbol.script_path = path;
@@ -231,36 +351,14 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
r_symbol.children.push_back(symbol);
} break;
- case ClassNode::Member::ENUM_VALUE: {
- lsp::DocumentSymbol symbol;
-
- symbol.name = m.enum_value.identifier->name;
- symbol.kind = lsp::SymbolKind::EnumMember;
- symbol.deprecated = false;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column);
- symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line));
- symbol.uri = uri;
- symbol.script_path = path;
-
- symbol.detail = symbol.name + " = " + itos(m.enum_value.value);
-
- r_symbol.children.push_back(symbol);
- } break;
case ClassNode::Member::SIGNAL: {
lsp::DocumentSymbol symbol;
symbol.name = m.signal->identifier->name;
symbol.kind = lsp::SymbolKind::Event;
symbol.deprecated = false;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column);
- symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line));
+ symbol.range = range_of_node(m.signal);
+ symbol.selectionRange = range_of_node(m.signal->identifier);
+ symbol.documentation = m.signal->doc_data.description;
symbol.uri = uri;
symbol.script_path = path;
symbol.detail = "signal " + String(m.signal->identifier->name) + "(";
@@ -272,17 +370,48 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
}
symbol.detail += ")";
+ for (GDScriptParser::ParameterNode *param : m.signal->parameters) {
+ lsp::DocumentSymbol param_symbol;
+ param_symbol.name = param->identifier->name;
+ param_symbol.kind = lsp::SymbolKind::Variable;
+ param_symbol.deprecated = false;
+ param_symbol.local = true;
+ param_symbol.range = range_of_node(param);
+ param_symbol.selectionRange = range_of_node(param->identifier);
+ param_symbol.uri = uri;
+ param_symbol.script_path = path;
+ param_symbol.detail = "var " + param_symbol.name;
+ if (param->get_datatype().is_hard_type()) {
+ param_symbol.detail += ": " + param->get_datatype().to_string();
+ }
+ symbol.children.push_back(param_symbol);
+ }
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::ENUM_VALUE: {
+ lsp::DocumentSymbol symbol;
+
+ 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.selectionRange = range_of_node(m.enum_value.identifier);
+ symbol.documentation = m.enum_value.doc_data.description;
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = symbol.name + " = " + itos(m.enum_value.value);
+
r_symbol.children.push_back(symbol);
} break;
case ClassNode::Member::ENUM: {
lsp::DocumentSymbol symbol;
+ symbol.name = m.m_enum->identifier->name;
symbol.kind = lsp::SymbolKind::Enum;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column);
- symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line));
+ symbol.range = range_of_node(m.m_enum);
+ symbol.selectionRange = range_of_node(m.m_enum->identifier);
+ symbol.documentation = m.m_enum->doc_data.description;
symbol.uri = uri;
symbol.script_path = path;
@@ -294,6 +423,25 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value);
}
symbol.detail += "}";
+
+ for (GDScriptParser::EnumNode::Value value : m.m_enum->values) {
+ lsp::DocumentSymbol child;
+
+ 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.selectionRange = range_of_node(value.identifier);
+ child.documentation = value.doc_data.description;
+ child.uri = uri;
+ child.script_path = path;
+
+ child.detail = child.name + " = " + itos(value.value);
+
+ symbol.children.push_back(child);
+ }
+
r_symbol.children.push_back(symbol);
} break;
case ClassNode::Member::FUNCTION: {
@@ -317,32 +465,29 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
const String uri = get_uri();
- r_symbol.name = p_func->identifier->name;
- r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method;
- r_symbol.detail = "func " + String(p_func->identifier->name) + "(";
+ bool is_named = p_func->identifier != nullptr;
+
+ r_symbol.name = is_named ? p_func->identifier->name : "";
+ r_symbol.kind = (p_func->is_static || p_func->source_lambda != nullptr) ? lsp::SymbolKind::Function : lsp::SymbolKind::Method;
+ r_symbol.detail = "func";
+ if (is_named) {
+ r_symbol.detail += " " + String(p_func->identifier->name);
+ }
+ r_symbol.detail += "(";
r_symbol.deprecated = false;
- r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
- r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column);
- r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
- r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column);
- r_symbol.selectionRange.start.line = r_symbol.range.start.line;
- r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line));
+ r_symbol.range = range_of_node(p_func);
+ if (is_named) {
+ r_symbol.selectionRange = range_of_node(p_func->identifier);
+ } else {
+ r_symbol.selectionRange.start = r_symbol.selectionRange.end = r_symbol.range.start;
+ }
+ r_symbol.documentation = p_func->doc_data.description;
r_symbol.uri = uri;
r_symbol.script_path = path;
String parameters;
for (int i = 0; i < p_func->parameters.size(); i++) {
const ParameterNode *parameter = p_func->parameters[i];
- lsp::DocumentSymbol symbol;
- symbol.kind = lsp::SymbolKind::Variable;
- symbol.name = parameter->identifier->name;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column);
- symbol.uri = uri;
- symbol.script_path = path;
- r_symbol.children.push_back(symbol);
if (i > 0) {
parameters += ", ";
}
@@ -387,6 +532,13 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
node_stack.push_back(while_node->loop);
} break;
+ case GDScriptParser::TypeNode::MATCH: {
+ GDScriptParser::MatchNode *match_node = (GDScriptParser::MatchNode *)node;
+ for (GDScriptParser::MatchBranchNode *branch_node : match_node->branches) {
+ node_stack.push_back(branch_node);
+ }
+ } break;
+
case GDScriptParser::TypeNode::MATCH_BRANCH: {
GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node;
node_stack.push_back(match_node->block);
@@ -400,20 +552,6 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
}
} break;
- case GDScriptParser::TypeNode::VARIABLE: {
- GDScriptParser::VariableNode *variable_node = (GDScriptParser::VariableNode *)(node);
- lsp::DocumentSymbol symbol;
- symbol.kind = lsp::SymbolKind::Variable;
- symbol.name = variable_node->identifier->name;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(variable_node->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(variable_node->start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(variable_node->end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(variable_node->end_column);
- symbol.uri = uri;
- symbol.script_path = path;
- r_symbol.children.push_back(symbol);
- } break;
-
default:
continue;
}
@@ -426,10 +564,40 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
lsp::DocumentSymbol symbol;
symbol.name = local.name;
symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
+ switch (local.type) {
+ case SuiteNode::Local::CONSTANT:
+ symbol.range = range_of_node(local.constant);
+ symbol.selectionRange = range_of_node(local.constant->identifier);
+ break;
+ case SuiteNode::Local::VARIABLE:
+ symbol.range = range_of_node(local.variable);
+ symbol.selectionRange = range_of_node(local.variable->identifier);
+ if (local.variable->initializer && local.variable->initializer->type == GDScriptParser::Node::LAMBDA) {
+ GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)local.variable->initializer;
+ lsp::DocumentSymbol lambda;
+ parse_function_symbol(lambda_node->function, lambda);
+ // Merge lambda into current variable.
+ // -> Only interested in new variables, not lambda itself.
+ symbol.children.append_array(lambda.children);
+ }
+ break;
+ case SuiteNode::Local::PARAMETER:
+ symbol.range = range_of_node(local.parameter);
+ symbol.selectionRange = range_of_node(local.parameter->identifier);
+ break;
+ case SuiteNode::Local::FOR_VARIABLE:
+ case SuiteNode::Local::PATTERN_BIND:
+ symbol.range = range_of_node(local.bind);
+ symbol.selectionRange = range_of_node(local.bind);
+ break;
+ default:
+ // Fallback.
+ symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines());
+ symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines());
+ symbol.selectionRange = symbol.range;
+ break;
+ }
+ symbol.local = true;
symbol.uri = uri;
symbol.script_path = path;
symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var ";
@@ -437,53 +605,19 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
if (local.get_datatype().is_hard_type()) {
symbol.detail += ": " + local.get_datatype().to_string();
}
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
- r_symbol.children.push_back(symbol);
- }
- }
-}
-
-String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
- ERR_FAIL_INDEX_V(p_line, lines.size(), String());
-
- List<String> doc_lines;
-
- if (!p_docs_down) { // inline comment
- String inline_comment = lines[p_line];
- int comment_start = inline_comment.find("##");
- if (comment_start != -1) {
- inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges();
- if (inline_comment.length() > 1) {
- doc_lines.push_back(inline_comment.substr(2, inline_comment.length()));
+ switch (local.type) {
+ case SuiteNode::Local::CONSTANT:
+ symbol.documentation = local.constant->doc_data.description;
+ break;
+ case SuiteNode::Local::VARIABLE:
+ symbol.documentation = local.variable->doc_data.description;
+ break;
+ default:
+ break;
}
+ r_symbol.children.push_back(symbol);
}
}
-
- int step = p_docs_down ? 1 : -1;
- int start_line = p_docs_down ? p_line : p_line - 1;
- for (int i = start_line; true; i += step) {
- if (i < 0 || i >= lines.size()) {
- break;
- }
-
- String line_comment = lines[i].strip_edges(true, false);
- if (line_comment.begins_with("##")) {
- line_comment = line_comment.substr(2, line_comment.length());
- if (p_docs_down) {
- doc_lines.push_back(line_comment);
- } else {
- doc_lines.push_front(line_comment);
- }
- } else {
- break;
- }
- }
-
- String doc;
- for (const String &E : doc_lines) {
- doc += E + "\n";
- }
- return doc;
}
String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const {
@@ -492,7 +626,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs
for (int i = 0; i < len; i++) {
if (i == p_cursor.line) {
longthing += lines[i].substr(0, p_cursor.character);
- longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ longthing += String::chr(0xFFFF); // Not unicode, represents the cursor.
longthing += lines[i].substr(p_cursor.character, lines[i].size());
} else {
longthing += lines[i];
@@ -513,7 +647,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c
if (i == p_cursor.line) {
String line = lines[i];
String first_part = line.substr(0, p_cursor.character);
- String last_part = line.substr(p_cursor.character + 1, lines[i].length());
+ String last_part = line.substr(p_cursor.character, lines[i].length());
if (!p_symbol.is_empty()) {
String left_cursor_text;
for (int c = p_cursor.character - 1; c >= 0; c--) {
@@ -527,9 +661,9 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c
}
longthing += first_part;
- longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ longthing += String::chr(0xFFFF); // Not unicode, represents the cursor.
if (p_func_required) {
- longthing += "("; // tell the parser this is a function call
+ longthing += "("; // Tell the parser this is a function call.
}
longthing += last_part;
} else {
@@ -544,7 +678,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c
return longthing;
}
-String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const {
+String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const {
ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
String line = lines[p_position.line];
if (line.is_empty()) {
@@ -552,8 +686,32 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
}
ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
- int start_pos = p_position.character;
- for (int c = p_position.character; c >= 0; c--) {
+ // `p_position` cursor is BETWEEN chars, not ON chars.
+ // ->
+ // ```gdscript
+ // var member| := some_func|(some_variable|)
+ // ^ ^ ^
+ // | | | cursor on `some_variable, position on `)`
+ // | |
+ // | | cursor on `some_func`, pos on `(`
+ // |
+ // | cursor on `member`, pos on ` ` (space)
+ // ```
+ // -> Move position to previous character if:
+ // * Position not on valid identifier char.
+ // * Prev position is valid identifier char.
+ lsp::Position pos = p_position;
+ if (
+ pos.character >= line.length() // Cursor at end of line.
+ || (!is_ascii_identifier_char(line[pos.character]) // Not on valid identifier char.
+ && (pos.character > 0 // Not line start -> there is a prev char.
+ && is_ascii_identifier_char(line[pos.character - 1]) // Prev is valid identifier char.
+ ))) {
+ pos.character--;
+ }
+
+ int start_pos = pos.character;
+ for (int c = pos.character; c >= 0; c--) {
start_pos = c;
char32_t ch = line[c];
bool valid_char = is_ascii_identifier_char(ch);
@@ -562,8 +720,8 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
}
}
- int end_pos = p_position.character;
- for (int c = p_position.character; c < line.length(); c++) {
+ int end_pos = pos.character;
+ for (int c = pos.character; c < line.length(); c++) {
char32_t ch = line[c];
bool valid_char = is_ascii_identifier_char(ch);
if (!valid_char) {
@@ -571,9 +729,11 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
}
end_pos = c;
}
+
if (start_pos < end_pos) {
- p_offset.x = start_pos - p_position.character;
- p_offset.y = end_pos - p_position.character;
+ r_range.start.line = r_range.end.line = pos.line;
+ r_range.start.character = start_pos + 1;
+ r_range.end.character = end_pos + 1;
return line.substr(start_pos + 1, end_pos - start_pos);
}
@@ -584,15 +744,15 @@ String ExtendGDScriptParser::get_uri() const {
return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
}
-const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const {
+const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name) const {
const lsp::DocumentSymbol *ret = nullptr;
if (p_line < p_parent.range.start.line) {
return ret;
- } else if (p_parent.range.start.line == p_line) {
+ } else if (p_parent.range.start.line == p_line && (p_symbol_name.is_empty() || p_parent.name == p_symbol_name)) {
return &p_parent;
} else {
for (int i = 0; i < p_parent.children.size(); i++) {
- ret = search_symbol_defined_at_line(p_line, p_parent.children[i]);
+ ret = search_symbol_defined_at_line(p_line, p_parent.children[i], p_symbol_name);
if (ret) {
break;
}
@@ -645,11 +805,11 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi
return ERR_METHOD_NOT_FOUND;
}
-const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
+const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line, const String &p_symbol_name) const {
if (p_line <= 0) {
return &class_symbol;
}
- return search_symbol_defined_at_line(p_line, class_symbol);
+ return search_symbol_defined_at_line(p_line, class_symbol, p_symbol_name);
}
const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const {
@@ -812,7 +972,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
api["name"] = m.signal->identifier->name;
Array pars;
for (int j = 0; j < m.signal->parameters.size(); j++) {
- pars.append(String(m.signal->parameters[i]->identifier->name));
+ pars.append(String(m.signal->parameters[j]->identifier->name));
}
api["arguments"] = pars;
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) {
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
index 4fd27de081..a808f19e5b 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.h
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -39,6 +39,9 @@
#ifndef LINE_NUMBER_TO_INDEX
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
#endif
+#ifndef COLUMN_NUMBER_TO_INDEX
+#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1)
+#endif
#ifndef SYMBOL_SEPERATOR
#define SYMBOL_SEPERATOR "::"
@@ -50,6 +53,64 @@
typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;
+/**
+ * Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`.
+ *
+ * Difference to `lsp::Position`:
+ * * Line & Char/column: 1-based
+ * * LSP: both 0-based
+ * * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`).
+ * * LSP: tab is single char
+ *
+ * Example:
+ * ```gdscript
+ * →→var my_value = 42
+ * ```
+ * `_` is at:
+ * * Godot: `column=12`
+ * * using `indent/size=4`
+ * * Note: counting starts at `1`
+ * * LSP: `character=8`
+ * * Note: counting starts at `0`
+ */
+struct GodotPosition {
+ int line;
+ int column;
+
+ GodotPosition(int p_line, int p_column) :
+ line(p_line), column(p_column) {}
+
+ lsp::Position to_lsp(const Vector<String> &p_lines) const;
+ static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines);
+
+ bool operator==(const GodotPosition &p_other) const {
+ return line == p_other.line && column == p_other.column;
+ }
+
+ String to_string() const {
+ return vformat("(%d,%d)", line, column);
+ }
+};
+
+struct GodotRange {
+ GodotPosition start;
+ GodotPosition end;
+
+ GodotRange(GodotPosition p_start, GodotPosition p_end) :
+ start(p_start), end(p_end) {}
+
+ lsp::Range to_lsp(const Vector<String> &p_lines) const;
+ static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines);
+
+ bool operator==(const GodotRange &p_other) const {
+ return start == p_other.start && end == p_other.end;
+ }
+
+ String to_string() const {
+ return vformat("[%s:%s]", start.to_string(), end.to_string());
+ }
+};
+
class ExtendGDScriptParser : public GDScriptParser {
String path;
Vector<String> lines;
@@ -60,6 +121,8 @@ class ExtendGDScriptParser : public GDScriptParser {
ClassMembers members;
HashMap<String, ClassMembers> inner_classes;
+ lsp::Range range_of_node(const GDScriptParser::Node *p_node) const;
+
void update_diagnostics();
void update_symbols();
@@ -70,8 +133,7 @@ class ExtendGDScriptParser : public GDScriptParser {
Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const;
Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const;
- String parse_documentation(int p_line, bool p_docs_down = false);
- const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const;
+ const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "") const;
Array member_completions;
@@ -87,10 +149,18 @@ public:
String get_text_for_completion(const lsp::Position &p_cursor) const;
String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const;
- String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
+ String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const;
String get_uri() const;
- const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const;
+ /**
+ * `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named.
+ *
+ * Necessary when multiple symbols at same line for example with `func`:
+ * `func handle_arg(arg: int):`
+ * -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted.
+ * With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`.
+ */
+ const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "") const;
const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
const List<lsp::DocumentLink> &get_document_links() const;
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 112db4df3a..8489fc08c1 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -278,6 +278,11 @@ void GDScriptLanguageProtocol::stop() {
}
void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) {
+#ifdef TESTS_ENABLED
+ if (clients.is_empty()) {
+ return;
+ }
+#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
@@ -285,7 +290,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
- ERR_FAIL_COND(peer == nullptr);
+ ERR_FAIL_NULL(peer);
Dictionary message = make_notification(p_method, p_params);
String msg = Variant(message).to_json_string();
@@ -294,6 +299,11 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}
void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) {
+#ifdef TESTS_ENABLED
+ if (clients.is_empty()) {
+ return;
+ }
+#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
@@ -301,7 +311,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
- ERR_FAIL_COND(peer == nullptr);
+ ERR_FAIL_NULL(peer);
Dictionary message = make_request(p_method, p_params, next_server_id);
next_server_id++;
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 062be0fe20..053be7eec2 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -36,6 +36,8 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+int GDScriptLanguageServer::port_override = -1;
+
GDScriptLanguageServer::GDScriptLanguageServer() {
_EDITOR_DEF("network/language_server/remote_host", host);
_EDITOR_DEF("network/language_server/remote_port", port);
@@ -62,7 +64,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
String remote_host = String(_EDITOR_GET("network/language_server/remote_host"));
- int remote_port = (int)_EDITOR_GET("network/language_server/remote_port");
+ int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
if (remote_host != host || remote_port != port || remote_use_thread != use_thread) {
stop();
@@ -73,6 +75,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
void GDScriptLanguageServer::thread_main(void *p_userdata) {
+ set_current_thread_safe_for_nodes(true);
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
@@ -83,10 +86,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {
void GDScriptLanguageServer::start() {
host = String(_EDITOR_GET("network/language_server/remote_host"));
- port = (int)_EDITOR_GET("network/language_server/remote_port");
+ port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
if (protocol.start(port, IPAddress(host)) == OK) {
- EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);
+ EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h
index 75f9403a74..e845d139bf 100644
--- a/modules/gdscript/language_server/gdscript_language_server.h
+++ b/modules/gdscript/language_server/gdscript_language_server.h
@@ -53,6 +53,7 @@ private:
void _notification(int p_what);
public:
+ static int port_override;
GDScriptLanguageServer();
void start();
void stop();
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 92a5f55978..d6779dc71c 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -50,6 +50,8 @@ void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename);
+ ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename);
+ ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
@@ -161,11 +163,8 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
Array arr;
if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
- Vector<lsp::DocumentedSymbolInformation> list;
- parser->value->get_symbols().symbol_tree_as_list(uri, list);
- for (int i = 0; i < list.size(); i++) {
- arr.push_back(list[i].to_json());
- }
+ lsp::DocumentSymbol symbol = parser->value->get_symbols();
+ arr.push_back(symbol.to_json(true));
}
return arr;
}
@@ -253,6 +252,48 @@ Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) {
return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name);
}
+Variant GDScriptTextDocument::prepareRename(const Dictionary &p_params) {
+ lsp::TextDocumentPositionParams params;
+ params.load(p_params);
+
+ lsp::DocumentSymbol symbol;
+ lsp::Range range;
+ if (GDScriptLanguageProtocol::get_singleton()->get_workspace()->can_rename(params, symbol, range)) {
+ return Variant(range.to_json());
+ }
+
+ // `null` -> rename not valid at current location.
+ return Variant();
+}
+
+Array GDScriptTextDocument::references(const Dictionary &p_params) {
+ Array res;
+
+ lsp::ReferenceParams params;
+ params.load(p_params);
+
+ const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
+ if (symbol) {
+ Vector<lsp::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_all_usages(*symbol);
+ res.resize(usages.size());
+ int declaration_adjustment = 0;
+ for (int i = 0; i < usages.size(); i++) {
+ lsp::Location usage = usages[i];
+ if (!params.context.includeDeclaration && usage.range == symbol->range) {
+ declaration_adjustment++;
+ continue;
+ }
+ res[i - declaration_adjustment] = usages[i].to_json();
+ }
+
+ if (declaration_adjustment > 0) {
+ res.resize(res.size() - declaration_adjustment);
+ }
+ }
+
+ return res;
+}
+
Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
lsp::CompletionItem item;
item.load(p_params);
@@ -305,6 +346,15 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
}
}
+ if (item.kind == lsp::CompletionItemKind::Method) {
+ bool is_trigger_character = params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter;
+ bool is_quote_character = params.context.triggerCharacter == "\"" || params.context.triggerCharacter == "'";
+
+ if (is_trigger_character && is_quote_character && item.insertText.is_quoted()) {
+ item.insertText = item.insertText.unquote();
+ }
+ }
+
return item.to_json(true);
}
@@ -450,7 +500,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
if (symbol) {
lsp::Location location;
location.uri = symbol->uri;
- location.range = symbol->range;
+ location.range = symbol->selectionRange;
const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
if (file_checker->file_exists(path)) {
arr.push_back(location.to_json());
@@ -464,7 +514,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
if (!s->uri.is_empty()) {
lsp::Location location;
location.uri = s->uri;
- location.range = s->range;
+ location.range = s->selectionRange;
arr.push_back(location.to_json());
r_list.push_back(s);
}
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index 0121101db2..cfd0490f0a 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -65,6 +65,8 @@ public:
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
+ Variant prepareRename(const Dictionary &p_params);
+ Array references(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
Array documentLink(const Dictionary &p_params);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 9f848b02f5..81933c8c87 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -46,7 +46,6 @@
void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files);
- ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);
@@ -182,35 +181,33 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::Do
return nullptr;
}
-const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) {
- const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols();
+const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position) {
+ // Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`.
- for (int i = 0; i < class_symbol->children.size(); ++i) {
- int kind = class_symbol->children[i].kind;
- switch (kind) {
- case lsp::SymbolKind::Function:
- case lsp::SymbolKind::Method:
- case lsp::SymbolKind::Class: {
- const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i];
+ const lsp::DocumentSymbol *current = &p_parser->get_symbols();
+ const lsp::DocumentSymbol *best_match = nullptr;
- for (int l = 0; l < function_symbol->children.size(); ++l) {
- const lsp::DocumentSymbol *local = &function_symbol->children[l];
- if (!local->detail.is_empty() && local->name == p_symbol_identifier) {
- return local;
- }
- }
- } break;
+ while (current) {
+ if (current->name == p_symbol_identifier) {
+ if (current->selectionRange.contains(p_position)) {
+ // Exact match: pos is ON symbol decl identifier.
+ return current;
+ }
- case lsp::SymbolKind::Variable: {
- const lsp::DocumentSymbol *variable_symbol = &class_symbol->children[i];
- if (variable_symbol->name == p_symbol_identifier) {
- return variable_symbol;
- }
- } break;
+ best_match = current;
+ }
+
+ const lsp::DocumentSymbol *parent = current;
+ current = nullptr;
+ for (const lsp::DocumentSymbol &child : parent->children) {
+ if (child.range.contains(p_position)) {
+ current = &child;
+ break;
+ }
}
}
- return nullptr;
+ return best_match;
}
void GDScriptWorkspace::reload_all_workspace_scripts() {
@@ -275,25 +272,6 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path)
return nullptr;
}
-Array GDScriptWorkspace::symbol(const Dictionary &p_params) {
- String query = p_params["query"];
- Array arr;
- if (!query.is_empty()) {
- for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
- Vector<lsp::DocumentedSymbolInformation> script_symbols;
- E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols);
- for (int i = 0; i < script_symbols.size(); ++i) {
- if (query.is_subsequence_ofn(script_symbols[i].name)) {
- lsp::DocumentedSymbolInformation symbol = script_symbols[i];
- symbol.location.uri = get_file_uri(symbol.location.uri);
- arr.push_back(symbol.to_json());
- }
- }
- }
- }
- return arr;
-}
-
Error GDScriptWorkspace::initialize() {
if (initialized) {
return OK;
@@ -423,7 +401,7 @@ Error GDScriptWorkspace::initialize() {
native_members.insert(E.key, members);
}
- // cache member completions
+ // Cache member completions.
for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) {
S.value->get_member_completions();
}
@@ -458,48 +436,110 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont
return err;
}
-Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) {
- Error err;
- String path = get_file_path(p_doc_pos.textDocument.uri);
+static bool is_valid_rename_target(const lsp::DocumentSymbol *p_symbol) {
+ // Must be valid symbol.
+ if (!p_symbol) {
+ return false;
+ }
+
+ // Cannot rename builtin.
+ if (!p_symbol->native_class.is_empty()) {
+ return false;
+ }
+
+ // Source must be available.
+ if (p_symbol->script_path.is_empty()) {
+ return false;
+ }
+ return true;
+}
+
+Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) {
lsp::WorkspaceEdit edit;
- List<String> paths;
- list_script_files("res://", paths);
+ const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
+ if (is_valid_rename_target(reference_symbol)) {
+ Vector<lsp::Location> usages = find_all_usages(*reference_symbol);
+ for (int i = 0; i < usages.size(); ++i) {
+ lsp::Location loc = usages[i];
+
+ edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name);
+ }
+ }
+
+ return edit.to_json();
+}
+bool GDScriptWorkspace::can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range) {
const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
- if (reference_symbol) {
- String identifier = reference_symbol->name;
+ if (!is_valid_rename_target(reference_symbol)) {
+ return false;
+ }
- for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) {
- PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n");
- for (int i = 0; i < content.size(); ++i) {
- String line = content[i];
+ String path = get_file_path(p_doc_pos.textDocument.uri);
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+ parser->get_identifier_under_position(p_doc_pos.position, r_range);
+ r_symbol = *reference_symbol;
+ return true;
+ }
- int character = line.find(identifier);
- while (character > -1) {
- lsp::TextDocumentPositionParams params;
+ return false;
+}
- lsp::TextDocumentIdentifier text_doc;
- text_doc.uri = get_file_uri(PE->get());
+Vector<lsp::Location> GDScriptWorkspace::find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path) {
+ Vector<lsp::Location> usages;
- params.textDocument = text_doc;
- params.position.line = i;
- params.position.character = character;
+ String identifier = p_symbol.name;
+ if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) {
+ const PackedStringArray &content = parser->get_lines();
+ for (int i = 0; i < content.size(); ++i) {
+ String line = content[i];
- const lsp::DocumentSymbol *other_symbol = resolve_symbol(params);
+ int character = line.find(identifier);
+ while (character > -1) {
+ lsp::TextDocumentPositionParams params;
- if (other_symbol == reference_symbol) {
- edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name);
- }
+ lsp::TextDocumentIdentifier text_doc;
+ text_doc.uri = get_file_uri(p_file_path);
- character = line.find(identifier, character + 1);
+ params.textDocument = text_doc;
+ params.position.line = i;
+ params.position.character = character;
+
+ const lsp::DocumentSymbol *other_symbol = resolve_symbol(params);
+
+ if (other_symbol == &p_symbol) {
+ lsp::Location loc;
+ loc.uri = text_doc.uri;
+ loc.range.start = params.position;
+ loc.range.end.line = params.position.line;
+ loc.range.end.character = params.position.character + identifier.length();
+ usages.append(loc);
}
+
+ character = line.find(identifier, character + 1);
}
}
}
- return edit.to_json();
+ return usages;
+}
+
+Vector<lsp::Location> GDScriptWorkspace::find_all_usages(const lsp::DocumentSymbol &p_symbol) {
+ if (p_symbol.local) {
+ // Only search in current document.
+ return find_usages_in_file(p_symbol, p_symbol.script_path);
+ }
+ // Search in all documents.
+ List<String> paths;
+ list_script_files("res://", paths);
+
+ Vector<lsp::Location> usages;
+ for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) {
+ usages.append_array(find_usages_in_file(p_symbol, PE->get()));
+ }
+ return usages;
}
Error GDScriptWorkspace::parse_local_script(const String &p_path) {
@@ -636,9 +676,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
lsp::Position pos = p_doc_pos.position;
if (symbol_identifier.is_empty()) {
- Vector2i offset;
- symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
- pos.character += offset.y;
+ lsp::Range range;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
+ pos.character = range.end.character;
}
if (!symbol_identifier.is_empty()) {
@@ -661,7 +701,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
}
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
- symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location));
+ symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
if (symbol) {
switch (symbol->kind) {
@@ -670,10 +710,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
symbol = get_parameter_symbol(symbol, symbol_identifier);
}
} break;
-
- case lsp::SymbolKind::Variable: {
- symbol = get_local_symbol(parser, symbol_identifier);
- } break;
}
}
}
@@ -686,10 +722,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
symbol = get_native_symbol(ret.class_name, member);
}
} else {
- symbol = parser->get_member_symbol(symbol_identifier);
-
+ symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position);
if (!symbol) {
- symbol = get_local_symbol(parser, symbol_identifier);
+ symbol = parser->get_member_symbol(symbol_identifier);
}
}
}
@@ -703,8 +738,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
String symbol_identifier;
- Vector2i offset;
- symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
+ lsp::Range range;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
for (const KeyValue<StringName, ClassMembers> &E : native_members) {
const ClassMembers &members = native_members.get(E.key);
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index 80653778fb..0b2d43b817 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -54,7 +54,7 @@ protected:
const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;
const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const;
const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier);
- const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier);
+ const lsp::DocumentSymbol *get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position);
void reload_all_workspace_scripts();
@@ -74,9 +74,6 @@ public:
HashMap<StringName, ClassMembers> native_members;
public:
- Array symbol(const Dictionary &p_params);
-
-public:
Error initialize();
Error parse_script(const String &p_path, const String &p_content);
@@ -96,6 +93,9 @@ public:
Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
void did_delete_files(const Dictionary &p_params);
Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name);
+ bool can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range);
+ Vector<lsp::Location> find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path);
+ Vector<lsp::Location> find_all_usages(const lsp::DocumentSymbol &p_symbol);
GDScriptWorkspace();
~GDScriptWorkspace();
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index b9a54cf818..e09adb74bd 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -83,6 +83,14 @@ struct Position {
*/
int character = 0;
+ _FORCE_INLINE_ bool operator==(const Position &p_other) const {
+ return line == p_other.line && character == p_other.character;
+ }
+
+ String to_string() const {
+ return vformat("(%d,%d)", line, character);
+ }
+
_FORCE_INLINE_ void load(const Dictionary &p_params) {
line = p_params["line"];
character = p_params["character"];
@@ -112,6 +120,27 @@ struct Range {
*/
Position end;
+ _FORCE_INLINE_ bool operator==(const Range &p_other) const {
+ return start == p_other.start && end == p_other.end;
+ }
+
+ bool contains(const Position &p_pos) const {
+ // Inside line range.
+ if (start.line <= p_pos.line && p_pos.line <= end.line) {
+ // If on start line: must come after start char.
+ bool start_ok = p_pos.line == start.line ? start.character <= p_pos.character : true;
+ // If on end line: must come before end char.
+ bool end_ok = p_pos.line == end.line ? p_pos.character <= end.character : true;
+ return start_ok && end_ok;
+ } else {
+ return false;
+ }
+ }
+
+ String to_string() const {
+ return vformat("[%s:%s]", start.to_string(), end.to_string());
+ }
+
_FORCE_INLINE_ void load(const Dictionary &p_params) {
start.load(p_params["start"]);
end.load(p_params["end"]);
@@ -203,6 +232,17 @@ struct TextDocumentPositionParams {
}
};
+struct ReferenceContext {
+ /**
+ * Include the declaration of the current symbol.
+ */
+ bool includeDeclaration;
+};
+
+struct ReferenceParams : TextDocumentPositionParams {
+ ReferenceContext context;
+};
+
struct DocumentLinkParams {
/**
* The document to provide document links for.
@@ -343,8 +383,8 @@ struct Command {
}
};
-// Use namespace instead of enumeration to follow the LSP specifications
-// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+// Use namespace instead of enumeration to follow the LSP specifications.
+// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not.
namespace TextDocumentSyncKind {
/**
@@ -436,7 +476,7 @@ struct RenameOptions {
/**
* Renames should be checked and tested before being executed.
*/
- bool prepareProvider = false;
+ bool prepareProvider = true;
Dictionary to_json() {
Dictionary dict;
@@ -794,12 +834,12 @@ static const String Markdown = "markdown";
*/
struct MarkupContent {
/**
- * The type of the Markup
+ * The type of the Markup.
*/
String kind;
/**
- * The content itself
+ * The content itself.
*/
String value;
@@ -821,8 +861,8 @@ struct MarkupContent {
};
// Use namespace instead of enumeration to follow the LSP specifications
-// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
-// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc.
+// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not.
+// And here C++ compilers are unhappy with our enumeration name like `Color`, `File`, `RefCounted` etc.
/**
* The kind of a completion entry.
*/
@@ -854,7 +894,7 @@ static const int Operator = 24;
static const int TypeParameter = 25;
}; // namespace CompletionItemKind
-// Use namespace instead of enumeration to follow the LSP specifications
+// Use namespace instead of enumeration to follow the LSP specifications.
/**
* Defines whether the insert text in a completion item should be interpreted as
* plain text or a snippet.
@@ -1070,8 +1110,8 @@ struct CompletionList {
};
// Use namespace instead of enumeration to follow the LSP specifications
-// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
-// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc
+// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not
+// And here C++ compilers are unhappy with our enumeration name like `String`, `Array`, `Object` etc
/**
* A symbol kind.
*/
@@ -1105,70 +1145,6 @@ static const int TypeParameter = 26;
}; // namespace SymbolKind
/**
- * Represents information about programming constructs like variables, classes,
- * interfaces etc.
- */
-struct SymbolInformation {
- /**
- * The name of this symbol.
- */
- String name;
-
- /**
- * The kind of this symbol.
- */
- int kind = SymbolKind::File;
-
- /**
- * Indicates if this symbol is deprecated.
- */
- bool deprecated = false;
-
- /**
- * The location of this symbol. The location's range is used by a tool
- * to reveal the location in the editor. If the symbol is selected in the
- * tool the range's start information is used to position the cursor. So
- * the range usually spans more then the actual symbol's name and does
- * normally include things like visibility modifiers.
- *
- * The range doesn't have to denote a node range in the sense of a abstract
- * syntax tree. It can therefore not be used to re-construct a hierarchy of
- * the symbols.
- */
- Location location;
-
- /**
- * The name of the symbol containing this symbol. This information is for
- * user interface purposes (e.g. to render a qualifier in the user interface
- * if necessary). It can't be used to re-infer a hierarchy for the document
- * symbols.
- */
- String containerName;
-
- _FORCE_INLINE_ Dictionary to_json() const {
- Dictionary dict;
- dict["name"] = name;
- dict["kind"] = kind;
- dict["deprecated"] = deprecated;
- dict["location"] = location.to_json();
- dict["containerName"] = containerName;
- return dict;
- }
-};
-
-struct DocumentedSymbolInformation : public SymbolInformation {
- /**
- * A human-readable string with additional information
- */
- String detail;
-
- /**
- * A human-readable string that represents a doc-comment.
- */
- String documentation;
-};
-
-/**
* Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be
* hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range,
* e.g. the range of an identifier.
@@ -1186,12 +1162,12 @@ struct DocumentSymbol {
String detail;
/**
- * Documentation for this symbol
+ * Documentation for this symbol.
*/
String documentation;
/**
- * Class name for the native symbols
+ * Class name for the native symbols.
*/
String native_class;
@@ -1206,6 +1182,13 @@ struct DocumentSymbol {
bool deprecated = false;
/**
+ * If `true`: Symbol is local to script and cannot be accessed somewhere else.
+ *
+ * For example: local variable inside a `func`.
+ */
+ bool local = false;
+
+ /**
* The range enclosing this symbol not including leading/trailing whitespace but everything else
* like comments. This information is typically used to determine if the clients cursor is
* inside the symbol to reveal in the symbol in the UI.
@@ -1238,35 +1221,21 @@ struct DocumentSymbol {
dict["documentation"] = documentation;
dict["native_class"] = native_class;
}
- Array arr;
- arr.resize(children.size());
- for (int i = 0; i < children.size(); i++) {
- arr[i] = children[i].to_json(with_doc);
+ if (!children.is_empty()) {
+ Array arr;
+ for (int i = 0; i < children.size(); i++) {
+ if (children[i].local) {
+ continue;
+ }
+ arr.push_back(children[i].to_json(with_doc));
+ }
+ if (!children.is_empty()) {
+ dict["children"] = arr;
+ }
}
- dict["children"] = arr;
return dict;
}
- void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const {
- DocumentedSymbolInformation si;
- if (p_join_name && !p_container.is_empty()) {
- si.name = p_container + ">" + name;
- } else {
- si.name = name;
- }
- si.kind = kind;
- si.containerName = p_container;
- si.deprecated = deprecated;
- si.location.uri = p_uri;
- si.location.range = range;
- si.detail = detail;
- si.documentation = documentation;
- r_list.push_back(si);
- for (int i = 0; i < children.size(); i++) {
- children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name);
- }
- }
-
_FORCE_INLINE_ MarkupContent render() const {
MarkupContent markdown;
if (detail.length()) {
@@ -1460,6 +1429,17 @@ struct CompletionParams : public TextDocumentPositionParams {
TextDocumentPositionParams::load(p_params);
context.load(p_params["context"]);
}
+
+ Dictionary to_json() {
+ Dictionary ctx;
+ ctx["triggerCharacter"] = context.triggerCharacter;
+ ctx["triggerKind"] = context.triggerKind;
+
+ Dictionary dict;
+ dict = TextDocumentPositionParams::to_json();
+ dict["context"] = ctx;
+ return dict;
+ }
};
/**
@@ -1566,7 +1546,7 @@ struct SignatureHelp {
/**
* The active signature. If omitted or the value lies outside the
* range of `signatures` the value defaults to zero or is ignored if
- * `signatures.length === 0`. Whenever possible implementors should
+ * `signatures.length === 0`. Whenever possible implementers should
* make an active decision about the active signature and shouldn't
* rely on a default value.
* In future version of the protocol this property might become
@@ -1750,7 +1730,7 @@ struct ServerCapabilities {
/**
* The server provides find references support.
*/
- bool referencesProvider = false;
+ bool referencesProvider = true;
/**
* The server provides document highlight support.
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 874cbc6ee8..f91dc83f2c 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -149,6 +149,10 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l
// Set all warning levels to "Warn" in order to test them properly, even the ones that default to error.
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
+ if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) {
+ // TODO: Add ability for test scripts to specify which warnings to enable/disable for testing.
+ continue;
+ }
String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i);
ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN);
}
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd
new file mode 100644
index 0000000000..87d1b9ea18
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd
@@ -0,0 +1,7 @@
+# GH-73283
+
+class MyClass:
+ pass
+
+func test():
+ MyClass.not_existing_method()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out
new file mode 100644
index 0000000000..7340058dd4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Static function "not_existing_method()" not found in base "MyClass".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd
new file mode 100644
index 0000000000..7e1efb8d1b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd
@@ -0,0 +1,2 @@
+func test():
+ Time.new()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out
new file mode 100644
index 0000000000..bc4875d908
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot construct native class "Time" because it is an engine singleton.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd
new file mode 100644
index 0000000000..a46b6d8658
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd
@@ -0,0 +1,6 @@
+# GH-82081
+
+extends Time
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out
new file mode 100644
index 0000000000..7c26dea56e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot inherit native class "Time" because it is an engine singleton.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
new file mode 100644
index 0000000000..7e3b6e3c39
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Resource] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
new file mode 100644
index 0000000000..8f8a368f9a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Unable to iterate on value of type "Array[Resource]" with variable of type "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd
new file mode 100644
index 0000000000..db3f3f4c72
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd
@@ -0,0 +1,5 @@
+# GH-82021
+
+func test():
+ for x: String in [1, 2, 3]:
+ print(x)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out
new file mode 100644
index 0000000000..0bb654e7e2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "int" as "String".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd
new file mode 100644
index 0000000000..fdf22f6843
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd
@@ -0,0 +1,10 @@
+class A:
+ func f(_p: Object):
+ pass
+
+class B extends A:
+ func f(_p: Node):
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out
new file mode 100644
index 0000000000..c6a7e40e8c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd
new file mode 100644
index 0000000000..e4094f1d76
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd
@@ -0,0 +1,10 @@
+class A:
+ func f(_p: Variant):
+ pass
+
+class B extends A:
+ func f(_p: Node): # No `is_type_compatible()` misuse.
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out
new file mode 100644
index 0000000000..52a6efc6fc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd
new file mode 100644
index 0000000000..17663da4f6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd
@@ -0,0 +1,10 @@
+class A:
+ func f(_p: int):
+ pass
+
+class B extends A:
+ func f(_p: float): # No implicit conversion.
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out
new file mode 100644
index 0000000000..7a6207fd45
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f(int) -> Variant".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd
new file mode 100644
index 0000000000..6dfa75ecbc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd
@@ -0,0 +1,10 @@
+class A:
+ func f() -> Node:
+ return null
+
+class B extends A:
+ func f() -> Object:
+ return null
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out
new file mode 100644
index 0000000000..e680b2bd77
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f() -> Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd
new file mode 100644
index 0000000000..366494b94f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd
@@ -0,0 +1,10 @@
+class A:
+ func f() -> Node:
+ return null
+
+class B extends A:
+ func f() -> Variant: # No `is_type_compatible()` misuse.
+ return null
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out
new file mode 100644
index 0000000000..e680b2bd77
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f() -> Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd
new file mode 100644
index 0000000000..2cb4e7c616
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd
@@ -0,0 +1,10 @@
+class A:
+ func f() -> Node:
+ return null
+
+class B extends A:
+ func f() -> void: # No `is_type_compatible()` misuse.
+ return
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out
new file mode 100644
index 0000000000..e680b2bd77
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f() -> Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd
new file mode 100644
index 0000000000..2cabce46f5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd
@@ -0,0 +1,10 @@
+class A:
+ func f() -> float:
+ return 0.0
+
+class B extends A:
+ func f() -> int: # No implicit conversion.
+ return 0
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out
new file mode 100644
index 0000000000..72f2c493d4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The function signature doesn't match the parent. Parent signature is "f() -> float".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
new file mode 100644
index 0000000000..caeea46977
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
@@ -0,0 +1,9 @@
+# GH-75645
+
+extends Node
+
+static func static_func():
+ var a = $Node
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
new file mode 100644
index 0000000000..1910b3e66b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use shorthand "get_node()" notation ("$") in a static function.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd
new file mode 100644
index 0000000000..4b72fb9daa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd
@@ -0,0 +1,5 @@
+var f = (func (_a): return 0).call(x)
+var x = f
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out
new file mode 100644
index 0000000000..6bca25b330
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not resolve member "f": Cyclic reference.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd
new file mode 100644
index 0000000000..115e8be50a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd
@@ -0,0 +1,5 @@
+var f = func (_a = x): return 0
+var x = f
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out
new file mode 100644
index 0000000000..6bca25b330
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not resolve member "f": Cyclic reference.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd
new file mode 100644
index 0000000000..7cdc14685f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd
@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+ var e: E
+ const E = MyEnum
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out
new file mode 100644
index 0000000000..e1d5837f32
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not resolved at this point.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd
new file mode 100644
index 0000000000..68cf5efd8b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd
@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+ var E = MyEnum
+ var e: E
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out
new file mode 100644
index 0000000000..b1f4e7a2c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local variable "E" cannot be used as a type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd
new file mode 100644
index 0000000000..ac446183cb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd
@@ -0,0 +1,5 @@
+enum MyEnum {A}
+
+func test():
+ const E = MyEnum.A
+ var e: E
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out
new file mode 100644
index 0000000000..c3c2c8ca2f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not a valid type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd
new file mode 100644
index 0000000000..1dcb9fc36a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd
@@ -0,0 +1,4 @@
+func test():
+ match 0:
+ _ when a == 0:
+ print("a does not exist")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out
new file mode 100644
index 0000000000..c5f0a90d6a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Identifier "a" not declared in the current scope.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd
new file mode 100644
index 0000000000..1600c3001f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd
@@ -0,0 +1,4 @@
+# GH-73213
+
+func test():
+ print(Object())
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out
new file mode 100644
index 0000000000..27668fcd48
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid constructor "Object()", use "Object.new()" instead.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd
index 25cde1d40b..7cc5aaf44f 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd
@@ -1,4 +1,4 @@
func test():
- var unconvertable := 1
- var typed: Array[Object] = [unconvertable]
+ var unconvertible := 1
+ var typed: Array[Object] = [unconvertible]
print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd
new file mode 100644
index 0000000000..57dfffdbee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd
@@ -0,0 +1,5 @@
+func _init():
+ super()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out
new file mode 100644
index 0000000000..e68759223c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call the parent class' virtual function "_init()" because it hasn't been defined.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
new file mode 100644
index 0000000000..dafd2ec0c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -0,0 +1,17 @@
+class_name TestExportEnumAsDictionary
+
+enum MyEnum {A, B, C}
+
+const Utils = preload("../../utils.notest.gd")
+
+@export var x1 = MyEnum
+@export var x2 = MyEnum.A
+@export var x3 := MyEnum
+@export var x4 := MyEnum.A
+@export var x5: MyEnum
+
+func test():
+ for property in get_property_list():
+ if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
+ print(Utils.get_property_signature(property))
+ print(" ", Utils.get_property_additional_info(property))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
new file mode 100644
index 0000000000..f1a13f1045
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+@export var x1: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x2: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x3: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x4: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x5: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd
new file mode 100644
index 0000000000..a43c233625
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd
@@ -0,0 +1,20 @@
+class A:
+ func int_to_variant(_p: int): pass
+ func node_to_variant(_p: Node): pass
+ func node_2d_to_node(_p: Node2D): pass
+
+ func variant_to_untyped(_p: Variant): pass
+ func int_to_untyped(_p: int): pass
+ func node_to_untyped(_p: Node): pass
+
+class B extends A:
+ func int_to_variant(_p: Variant): pass
+ func node_to_variant(_p: Variant): pass
+ func node_2d_to_node(_p: Node): pass
+
+ func variant_to_untyped(_p): pass
+ func int_to_untyped(_p): pass
+ func node_to_untyped(_p): pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd
new file mode 100644
index 0000000000..4de50b6731
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd
@@ -0,0 +1,32 @@
+class A:
+ func variant_to_int() -> Variant: return 0
+ func variant_to_node() -> Variant: return null
+ func node_to_node_2d() -> Node: return null
+
+ func untyped_to_void(): pass
+ func untyped_to_variant(): pass
+ func untyped_to_int(): pass
+ func untyped_to_node(): pass
+
+ func void_to_untyped() -> void: pass
+ func variant_to_untyped() -> Variant: return null
+ func int_to_untyped() -> int: return 0
+ func node_to_untyped() -> Node: return null
+
+class B extends A:
+ func variant_to_int() -> int: return 0
+ func variant_to_node() -> Node: return null
+ func node_to_node_2d() -> Node2D: return null
+
+ func untyped_to_void() -> void: pass
+ func untyped_to_variant() -> Variant: return null
+ func untyped_to_int() -> int: return 0
+ func untyped_to_node() -> Node: return null
+
+ func void_to_untyped(): pass
+ func variant_to_untyped(): pass
+ func int_to_untyped(): pass
+ func node_to_untyped(): pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
index b447180ea8..d0f895d784 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
@@ -23,6 +23,7 @@ func test() -> void:
typed = variant()
inferred = variant()
+ @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown.
param_weak(typed)
param_typed(typed)
param_inferred(typed)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd
new file mode 100644
index 0000000000..51c589b8e0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd
@@ -0,0 +1,21 @@
+# GH-80508
+
+class A:
+ func a():
+ return A.new()
+ func b():
+ return B.new()
+
+class B:
+ func a():
+ return A.new()
+ func b():
+ return B.new()
+
+func test():
+ var a := A.new()
+ var b := B.new()
+ print(a.a() is A)
+ print(a.b() is B)
+ print(b.a() is A)
+ print(b.b() is B)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out
new file mode 100644
index 0000000000..f9783e4362
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+true
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd
new file mode 100644
index 0000000000..e2f41a652c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd
@@ -0,0 +1,34 @@
+# GH-70592
+
+var f: Callable = func ():
+ x = 2
+ return 1
+
+var x: int = f.call()
+
+var g: Array[Callable] = [
+ func ():
+ y += 10
+ return 1,
+ func ():
+ y += 20
+ return 2,
+]
+
+var y: int = g[0].call() + g[1].call()
+
+func test():
+ print(x)
+ f.call()
+ print(x)
+
+ print(y)
+ g[0].call()
+ g[1].call()
+ print(y)
+
+ # This prevents memory leak in CI. TODO: Investigate it.
+ # Also you cannot run the `EditorScript` twice without the cleaning. Error:
+ # Condition "!p_keep_state && has_instances" is true. Returning: ERR_ALREADY_IN_USE
+ f = Callable()
+ g.clear()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out
new file mode 100644
index 0000000000..6917fa7c2c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+1
+2
+3
+33
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd
new file mode 100644
index 0000000000..90c7f893b1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd
@@ -0,0 +1,41 @@
+class InnerClass:
+ enum InnerEnum {A = 2}
+ const INNER_CONST = "INNER_CONST"
+
+enum Enum {A = 1}
+
+const Other = preload("./local_const_as_type.notest.gd")
+
+func test():
+ const IC = InnerClass
+ const IE = IC.InnerEnum
+ const E = Enum
+ # Doesn't work in CI, but works in the editor. Looks like an unrelated bug. TODO: Investigate it.
+ # Error: Invalid call. Nonexistent function 'new' in base 'GDScript'.
+ var a1: IC = null # IC.new()
+ var a2: IE = IE.A
+ var a3: IC.InnerEnum = IE.A
+ var a4: E = E.A
+ print(a1.INNER_CONST)
+ print(a2)
+ print(a3)
+ print(a4)
+
+ const O = Other
+ const OV: Variant = Other # Removes metatype.
+ const OIC = O.InnerClass
+ const OIE = OIC.InnerEnum
+ const OE = O.Enum
+ var b: O = O.new()
+ @warning_ignore("unsafe_method_access")
+ var bv: OV = OV.new()
+ var b1: OIC = OIC.new()
+ var b2: OIE = OIE.A
+ var b3: O.InnerClass.InnerEnum = OIE.A
+ var b4: OE = OE.A
+ print(b.CONST)
+ print(bv.CONST)
+ print(b1.INNER_CONST)
+ print(b2)
+ print(b3)
+ print(b4)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd
new file mode 100644
index 0000000000..f16cdc18d8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd
@@ -0,0 +1,6 @@
+class InnerClass:
+ enum InnerEnum {A = 20}
+ const INNER_CONST = "OTHER_INNER_CONST"
+
+enum Enum {A = 10}
+const CONST = "OTHER_CONST"
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out
new file mode 100644
index 0000000000..b00024de2c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+INNER_CONST
+2
+2
+1
+OTHER_CONST
+OTHER_CONST
+OTHER_INNER_CONST
+20
+20
+10
diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd
index 5a413e2015..08e7dc590e 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd
@@ -6,10 +6,12 @@ var prop = null
func check_arg(arg = null) -> void:
if arg != null:
+ @warning_ignore("unsafe_call_argument")
print(check(arg))
func check_recur() -> void:
if recur != null:
+ @warning_ignore("unsafe_call_argument")
print(check(recur))
else:
recur = 1
@@ -22,11 +24,13 @@ func test() -> void:
if prop == null:
set('prop', 1)
+ @warning_ignore("unsafe_call_argument")
print(check(prop))
set('prop', null)
var loop = null
while loop != 2:
if loop != null:
+ @warning_ignore("unsafe_call_argument")
print(check(loop))
loop = 1 if loop == null else 2
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
new file mode 100644
index 0000000000..e1a1f07e47
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
@@ -0,0 +1,22 @@
+var _typed_array: Array[int]
+
+func weak_param_func(weak_param = _typed_array):
+ weak_param = [11] # Don't treat the literal as typed!
+ return weak_param
+
+func hard_param_func(hard_param := _typed_array):
+ hard_param = [12]
+ return hard_param
+
+func test():
+ var weak_var = _typed_array
+ print(weak_var.is_typed())
+ weak_var = [21] # Don't treat the literal as typed!
+ print(weak_var.is_typed())
+ print(weak_param_func().is_typed())
+
+ var hard_var := _typed_array
+ print(hard_var.is_typed())
+ hard_var = [22]
+ print(hard_var.is_typed())
+ print(hard_param_func().is_typed())
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
new file mode 100644
index 0000000000..34b18dbe7c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+false
+false
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
new file mode 100644
index 0000000000..a8641e4f3b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
@@ -0,0 +1,21 @@
+class BaseClass:
+ func _get_property_list():
+ return {"property" : "definition"}
+
+class SuperClassMethodsRecognized extends BaseClass:
+ func _init():
+ # Recognizes super class methods.
+ var _x = _get_property_list()
+
+class SuperMethodsRecognized extends BaseClass:
+ func _get_property_list():
+ # Recognizes super method.
+ var result = super()
+ result["new"] = "new"
+ return result
+
+func test():
+ var test1 = SuperClassMethodsRecognized.new()
+ print(test1._get_property_list()) # Calls base class's method.
+ var test2 = SuperMethodsRecognized.new()
+ print(test2._get_property_list())
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out
new file mode 100644
index 0000000000..ccff660117
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "property": "definition" }
+{ "property": "definition", "new": "new" }
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd
index 849df0921e..c1776fe1b4 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd
@@ -14,4 +14,5 @@ func test():
func do_add_node():
var node = Node.new()
node.name = "Node"
+ @warning_ignore("unsafe_call_argument")
add_child(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
index 29239a19da..939e787ea5 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
@@ -1,3 +1,5 @@
+class_name ShadowedClass
+
var member: int = 0
var print_debug := 'print_debug'
@@ -12,5 +14,6 @@ func test():
var sqrt := 'sqrt'
var member := 'member'
var reference := 'reference'
+ var ShadowedClass := 'ShadowedClass'
print('warn')
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
index accc791d8a..8297eed4b8 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
@@ -1,30 +1,34 @@
GDTEST_OK
>> WARNING
->> Line: 3
+>> Line: 5
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "print_debug" has the same name as a built-in function.
>> WARNING
->> Line: 9
+>> Line: 11
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Array" has the same name as a built-in type.
>> WARNING
->> Line: 10
+>> Line: 12
>> SHADOWED_GLOBAL_IDENTIFIER
->> The variable "Node" has the same name as a global class.
+>> The variable "Node" has the same name as a native class.
>> WARNING
->> Line: 11
+>> Line: 13
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "is_same" has the same name as a built-in function.
>> WARNING
->> Line: 12
+>> Line: 14
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "sqrt" has the same name as a built-in function.
>> WARNING
->> Line: 13
+>> Line: 15
>> SHADOWED_VARIABLE
->> The local variable "member" is shadowing an already-declared variable at line 1.
+>> The local variable "member" is shadowing an already-declared variable at line 3.
>> WARNING
->> Line: 14
+>> Line: 16
>> SHADOWED_VARIABLE_BASE_CLASS
>> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted".
+>> WARNING
+>> Line: 17
+>> SHADOWED_GLOBAL_IDENTIFIER
+>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
warn
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
new file mode 100644
index 0000000000..573060ae0f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
@@ -0,0 +1,37 @@
+func variant_func(x: Variant) -> void:
+ print(x)
+
+func int_func(x: int) -> void:
+ print(x)
+
+func float_func(x: float) -> void:
+ print(x)
+
+# We don't want to execute it because of errors, just analyze.
+func no_exec_test():
+ var untyped_int = 42
+ var untyped_string = "abc"
+ var variant_int: Variant = 42
+ var variant_string: Variant = "abc"
+ var typed_int: int = 42
+
+ variant_func(untyped_int) # No warning.
+ variant_func(untyped_string) # No warning.
+ variant_func(variant_int) # No warning.
+ variant_func(variant_string) # No warning.
+ variant_func(typed_int) # No warning.
+
+ int_func(untyped_int)
+ int_func(untyped_string)
+ int_func(variant_int)
+ int_func(variant_string)
+ int_func(typed_int) # No warning.
+
+ float_func(untyped_int)
+ float_func(untyped_string)
+ float_func(variant_int)
+ float_func(variant_string)
+ float_func(typed_int) # No warning.
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
new file mode 100644
index 0000000000..b8fcb67158
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
@@ -0,0 +1,33 @@
+GDTEST_OK
+>> WARNING
+>> Line: 24
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 25
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 26
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 27
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 30
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 31
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 32
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 33
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.notest.gd
new file mode 100644
index 0000000000..53d0b14d72
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/class.notest.gd
@@ -0,0 +1,132 @@
+extends Node
+
+class Inner1 extends Node:
+# ^^^^^^ class1 -> class1
+ var member1 := 42
+ # ^^^^^^^ class1:member1 -> class1:member1
+ var member2 : int = 13
+ # ^^^^^^^ class1:member2 -> class1:member2
+ var member3 = 1337
+ # ^^^^^^^ class1:member3 -> class1:member3
+
+ signal changed(old, new)
+ # ^^^^^^^ class1:signal -> class1:signal
+ func my_func(arg1: int, arg2: String, arg3):
+ # | | | | | | ^^^^ class1:func:arg3 -> class1:func:arg3
+ # | | | | ^^^^ class1:func:arg2 -> class1:func:arg2
+ # | | ^^^^ class1:func:arg1 -> class1:func:arg1
+ # ^^^^^^^ class1:func -> class1:func
+ print(arg1, arg2, arg3)
+ # | | | | ^^^^ -> class1:func:arg3
+ # | | ^^^^ -> class1:func:arg2
+ # ^^^^ -> class1:func:arg1
+ changed.emit(arg1, arg3)
+ # | | | ^^^^ -> class1:func:arg3
+ # | ^^^^ -> class1:func:arg1
+ #<^^^^^ -> class1:signal
+ return arg1 + arg2.length() + arg3
+ # | | | | ^^^^ -> class1:func:arg3
+ # | | ^^^^ -> class1:func:arg2
+ # ^^^^ -> class1:func:arg1
+
+class Inner2:
+# ^^^^^^ class2 -> class2
+ var member1 := 42
+ # ^^^^^^^ class2:member1 -> class2:member1
+ var member2 : int = 13
+ # ^^^^^^^ class2:member2 -> class2:member2
+ var member3 = 1337
+ # ^^^^^^^ class2:member3 -> class2:member3
+
+ signal changed(old, new)
+ # ^^^^^^^ class2:signal -> class2:signal
+ func my_func(arg1: int, arg2: String, arg3):
+ # | | | | | | ^^^^ class2:func:arg3 -> class2:func:arg3
+ # | | | | ^^^^ class2:func:arg2 -> class2:func:arg2
+ # | | ^^^^ class2:func:arg1 -> class2:func:arg1
+ # ^^^^^^^ class2:func -> class2:func
+ print(arg1, arg2, arg3)
+ # | | | | ^^^^ -> class2:func:arg3
+ # | | ^^^^ -> class2:func:arg2
+ # ^^^^ -> class2:func:arg1
+ changed.emit(arg1, arg3)
+ # | | | ^^^^ -> class2:func:arg3
+ # | ^^^^ -> class2:func:arg1
+ #<^^^^^ -> class2:signal
+ return arg1 + arg2.length() + arg3
+ # | | | | ^^^^ -> class2:func:arg3
+ # | | ^^^^ -> class2:func:arg2
+ # ^^^^ -> class2:func:arg1
+
+class Inner3 extends Inner2:
+# | | ^^^^^^ -> class2
+# ^^^^^^ class3 -> class3
+ var whatever = "foo"
+ # ^^^^^^^^ class3:whatever -> class3:whatever
+
+ func _init():
+ # ^^^^^ class3:init
+ # Note: no self-ref check here: resolves to `Object._init`.
+ # usages of `Inner3.new()` DO resolve to this `_init`
+ pass
+
+ class NestedInInner3:
+ # ^^^^^^^^^^^^^^ class3:nested1 -> class3:nested1
+ var some_value := 42
+ # ^^^^^^^^^^ class3:nested1:some_value -> class3:nested1:some_value
+
+ class AnotherNestedInInner3 extends NestedInInner3:
+ #! | | ^^^^^^^^^^^^^^ -> class3:nested1
+ # ^^^^^^^^^^^^^^^^^^^^^ class3:nested2 -> class3:nested2
+ var another_value := 13
+ # ^^^^^^^^^^^^^ class3:nested2:another_value -> class3:nested2:another_value
+
+func _ready():
+ var inner1 = Inner1.new()
+ # | | ^^^^^^ -> class1
+ # ^^^^^^ func:class1 -> func:class1
+ var value1 = inner1.my_func(1,"",3)
+ # | | | | ^^^^^^^ -> class1:func
+ # | | ^^^^^^ -> func:class1
+ # ^^^^^^ func:class1:value1 -> func:class1:value1
+ var value2 = inner1.member3
+ # | | | | ^^^^^^^ -> class1:member3
+ # | | ^^^^^^ -> func:class1
+ # ^^^^^^ func:class1:value2 -> func:class1:value2
+ print(value1, value2)
+ # | | ^^^^^^ -> func:class1:value2
+ # ^^^^^^ -> func:class1:value1
+
+ var inner3 = Inner3.new()
+ # | | | | ^^^ -> class3:init
+ # | | ^^^^^^ -> class3
+ # ^^^^^^ func:class3 -> func:class3
+ print(inner3)
+ # ^^^^^^ -> func:class3
+
+ var nested1 = Inner3.NestedInInner3.new()
+ # | | | | ^^^^^^^^^^^^^^ -> class3:nested1
+ # | | ^^^^^^ -> class3
+ # ^^^^^^^ func:class3:nested1 -> func:class3:nested1
+ var value_nested1 = nested1.some_value
+ # | | | | ^^^^^^^^^^ -> class3:nested1:some_value
+ # | | ^^^^^^^ -> func:class3:nested1
+ # ^^^^^^^^^^^^^ func:class3:nested1:value
+ print(value_nested1)
+ # ^^^^^^^^^^^^^ -> func:class3:nested1:value
+
+ var nested2 = Inner3.AnotherNestedInInner3.new()
+ # | | | | ^^^^^^^^^^^^^^^^^^^^^ -> class3:nested2
+ # | | ^^^^^^ -> class3
+ # ^^^^^^^ func:class3:nested2 -> func:class3:nested2
+ var value_nested2 = nested2.some_value
+ # | | | | ^^^^^^^^^^ -> class3:nested1:some_value
+ # | | ^^^^^^^ -> func:class3:nested2
+ # ^^^^^^^^^^^^^ func:class3:nested2:value
+ var another_value_nested2 = nested2.another_value
+ # | | | | ^^^^^^^^^^^^^ -> class3:nested2:another_value
+ # | | ^^^^^^^ -> func:class3:nested2
+ # ^^^^^^^^^^^^^^^^^^^^^ func:class3:nested2:another_value_nested
+ print(value_nested2, another_value_nested2)
+ # | | ^^^^^^^^^^^^^^^^^^^^^ -> func:class3:nested2:another_value_nested
+ # ^^^^^^^^^^^^^ -> func:class3:nested2:value
diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.notest.gd
new file mode 100644
index 0000000000..38b9ec110a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/enums.notest.gd
@@ -0,0 +1,26 @@
+extends Node
+
+enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
+# | | | | ^^^^^^^^^ enum:unnamed:ally -> enum:unnamed:ally
+# | | ^^^^^^^^^^ enum:unnamed:enemy -> enum:unnamed:enemy
+# ^^^^^^^^^^^^ enum:unnamed:neutral -> enum:unnamed:neutral
+enum Named {THING_1, THING_2, ANOTHER_THING = -1}
+# | | | | | | ^^^^^^^^^^^^^ enum:named:thing3 -> enum:named:thing3
+# | | | | ^^^^^^^ enum:named:thing2 -> enum:named:thing2
+# | | ^^^^^^^ enum:named:thing1 -> enum:named:thing1
+# ^^^^^ enum:named -> enum:named
+
+func f(arg):
+ match arg:
+ UNIT_ENEMY: print(UNIT_ENEMY)
+ # | ^^^^^^^^^^ -> enum:unnamed:enemy
+ #<^^^^^^^^ -> enum:unnamed:enemy
+ Named.THING_2: print(Named.THING_2)
+ #! | | | | | ^^^^^^^ -> enum:named:thing2
+ # | | | ^^^^^ -> enum:named
+ #! | ^^^^^^^ -> enum:named:thing2
+ #<^^^ -> enum:named
+ _: print(UNIT_ENEMY, Named.ANOTHER_THING)
+ #! | | | | ^^^^^^^^^^^^^ -> enum:named:thing3
+ # | | ^^^^^ -> enum:named
+ # ^^^^^^^^^^ -> enum:unnamed:enemy
diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd
new file mode 100644
index 0000000000..c25d73a719
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd
@@ -0,0 +1,28 @@
+extends Node
+
+var root = 0
+# ^^^^ 0_indent -> 0_indent
+
+func a():
+ var alpha: int = root + 42
+ # | | ^^^^ -> 0_indent
+ # ^^^^^ 1_indent -> 1_indent
+ if alpha > 42:
+ # ^^^^^ -> 1_indent
+ var beta := alpha + 13
+ # | | ^^^^ -> 1_indent
+ # ^^^^ 2_indent -> 2_indent
+ if beta > alpha:
+ # | | ^^^^^ -> 1_indent
+ # ^^^^ -> 2_indent
+ var gamma = beta + 1
+ # | | ^^^^ -> 2_indent
+ # ^^^^^ 3_indent -> 3_indent
+ print(gamma)
+ # ^^^^^ -> 3_indent
+ print(beta)
+ # ^^^^ -> 2_indent
+ print(alpha)
+ # ^^^^^ -> 1_indent
+ print(root)
+ # ^^^^ -> 0_indent
diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd
new file mode 100644
index 0000000000..6f5d468eea
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd
@@ -0,0 +1,91 @@
+extends Node
+
+var lambda_member1 := func(alpha: int, beta): return alpha + beta
+# | | | | | | | | ^^^^ -> \1:beta
+# | | | | | | ^^^^^ -> \1:alpha
+# | | | | ^^^^ \1:beta -> \1:beta
+# | | ^^^^^ \1:alpha -> \1:alpha
+# ^^^^^^^^^^^^^^ \1 -> \1
+
+var lambda_member2 := func(alpha, beta: int) -> int:
+# | | | | | |
+# | | | | | |
+# | | | | ^^^^ \2:beta -> \2:beta
+# | | ^^^^^ \2:alpha -> \2:alpha
+# ^^^^^^^^^^^^^^ \2 -> \2
+ return alpha + beta
+ # | | ^^^^ -> \2:beta
+ # ^^^^^ -> \2:alpha
+
+var lambda_member3 := func add_values(alpha, beta): return alpha + beta
+# | | | | | | | | ^^^^ -> \3:beta
+# | | | | | | ^^^^^ -> \3:alpha
+# | | | | ^^^^ \3:beta -> \3:beta
+# | | ^^^^^ \3:alpha -> \3:alpha
+# ^^^^^^^^^^^^^^ \3 -> \3
+
+var lambda_multiline = func(alpha: int, beta: int) -> int:
+# | | | | | |
+# | | | | | |
+# | | | | ^^^^ \multi:beta -> \multi:beta
+# | | ^^^^^ \multi:alpha -> \multi:alpha
+# ^^^^^^^^^^^^^^^^ \multi -> \multi
+ print(alpha + beta)
+ # | | ^^^^ -> \multi:beta
+ # ^^^^^ -> \multi:alpha
+ var tmp = alpha + beta + 42
+ # | | | | ^^^^ -> \multi:beta
+ # | | ^^^^^ -> \multi:alpha
+ # ^^^ \multi:tmp -> \multi:tmp
+ print(tmp)
+ # ^^^ -> \multi:tmp
+ if tmp > 50:
+ # ^^^ -> \multi:tmp
+ tmp += alpha
+ # | ^^^^^ -> \multi:alpha
+ #<^ -> \multi:tmp
+ else:
+ tmp -= beta
+ # | ^^^^ -> \multi:beta
+ #<^ -> \multi:tmp
+ print(tmp)
+ # ^^^ -> \multi:tmp
+ return beta + tmp + alpha
+ # | | | | ^^^^^ -> \multi:alpha
+ # | | ^^^ -> \multi:tmp
+ # ^^^^ -> \multi:beta
+
+
+var some_name := "foo bar"
+# ^^^^^^^^^ member:some_name -> member:some_name
+
+func _ready() -> void:
+ var a = lambda_member1.call(1,2)
+ # ^^^^^^^^^^^^^^ -> \1
+ var b = lambda_member2.call(1,2)
+ # ^^^^^^^^^^^^^^ -> \2
+ var c = lambda_member3.call(1,2)
+ # ^^^^^^^^^^^^^^ -> \3
+ var d = lambda_multiline.call(1,2)
+ # ^^^^^^^^^^^^^^^^ -> \multi
+ print(a,b,c,d)
+
+ var lambda_local = func(alpha, beta): return alpha + beta
+ # | | | | | | | | ^^^^ -> \local:beta
+ # | | | | | | ^^^^^ -> \local:alpha
+ # | | | | ^^^^ \local:beta -> \local:beta
+ # | | ^^^^^ \local:alpha -> \local:alpha
+ # ^^^^^^^^^^^^ \local -> \local
+
+ var value := 42
+ # ^^^^^ local:value -> local:value
+ var lambda_capture = func(): return value + some_name.length()
+ # | | | | ^^^^^^^^^ -> member:some_name
+ # | | ^^^^^ -> local:value
+ # ^^^^^^^^^^^^^^ \capture -> \capture
+
+ var z = lambda_local.call(1,2)
+ # ^^^^^^^^^^^^ -> \local
+ var x = lambda_capture.call()
+ # ^^^^^^^^^^^^^^ -> \capture
+ print(z,x)
diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd
new file mode 100644
index 0000000000..b6cc46f7da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd
@@ -0,0 +1,25 @@
+extends Node
+
+var member := 2
+# ^^^^^^ member -> member
+
+func test_member() -> void:
+ var test := member + 42
+ # | | ^^^^^^ -> member
+ # ^^^^ test -> test
+ test += 3
+ #<^^ -> test
+ member += 5
+ #<^^^^ -> member
+ test = return_arg(test)
+ # | ^^^^ -> test
+ #<^^ -> test
+ print(test)
+ # ^^^^ -> test
+
+func return_arg(arg: int) -> int:
+# ^^^ arg -> arg
+ arg += 2
+ #<^ -> arg
+ return arg
+ # ^^^ -> arg \ No newline at end of file
diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.notest.gd
new file mode 100644
index 0000000000..8dfaee2e5b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/properties.notest.gd
@@ -0,0 +1,65 @@
+extends Node
+
+var prop1 := 42
+# ^^^^^ prop1 -> prop1
+var prop2 : int = 42
+# ^^^^^ prop2 -> prop2
+var prop3 := 42:
+# ^^^^^ prop3 -> prop3
+ get:
+ return prop3 + 13
+ # ^^^^^ -> prop3
+ set(value):
+ # ^^^^^ prop3:value -> prop3:value
+ prop3 = value - 13
+ # | ^^^^^ -> prop3:value
+ #<^^^ -> prop3
+var prop4: int:
+# ^^^^^ prop4 -> prop4
+ get:
+ return 42
+var prop5 := 42:
+# ^^^^^ prop5 -> prop5
+ set(value):
+ # ^^^^^ prop5:value -> prop5:value
+ prop5 = value - 13
+ # | ^^^^^ -> prop5:value
+ #<^^^ -> prop5
+
+var prop6:
+# ^^^^^ prop6 -> prop6
+ get = get_prop6,
+ # ^^^^^^^^^ -> get_prop6
+ set = set_prop6
+ # ^^^^^^^^^ -> set_prop6
+func get_prop6():
+# ^^^^^^^^^ get_prop6 -> get_prop6
+ return 42
+func set_prop6(value):
+# | | ^^^^^ set_prop6:value -> set_prop6:value
+# ^^^^^^^^^ set_prop6 -> set_prop6
+ print(value)
+ # ^^^^^ -> set_prop6:value
+
+var prop7:
+# ^^^^^ prop7 -> prop7
+ get = get_prop7
+ # ^^^^^^^^^ -> get_prop7
+func get_prop7():
+# ^^^^^^^^^ get_prop7 -> get_prop7
+ return 42
+
+var prop8:
+# ^^^^^ prop8 -> prop8
+ set = set_prop8
+ # ^^^^^^^^^ -> set_prop8
+func set_prop8(value):
+# | | ^^^^^ set_prop8:value -> set_prop8:value
+# ^^^^^^^^^ set_prop8 -> set_prop8
+ print(value)
+ # ^^^^^ -> set_prop8:value
+
+const const_var := 42
+# ^^^^^^^^^ const_var -> const_var
+static var static_var := 42
+# ^^^^^^^^^^ static_var -> static_var
diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd
new file mode 100644
index 0000000000..20b8fb9bd7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd
@@ -0,0 +1,106 @@
+extends Node
+
+var member := 2
+# ^^^^^^ public -> public
+
+signal some_changed(new_value)
+# | | ^^^^^^^^^ signal:parameter -> signal:parameter
+# ^^^^^^^^^^^^ signal -> signal
+var some_value := 42:
+# ^^^^^^^^^^ property -> property
+ get:
+ return some_value
+ # ^^^^^^^^^^ -> property
+ set(value):
+ # ^^^^^ property:set:value -> property:set:value
+ some_changed.emit(value)
+ # | ^^^^^ -> property:set:value
+ #<^^^^^^^^^^ -> signal
+ some_value = value
+ # | ^^^^^ -> property:set:value
+ #<^^^^^^^^ -> property
+
+func v():
+ var value := member + 2
+ # | | ^^^^^^ -> public
+ # ^^^^^ v:value -> v:value
+ print(value)
+ # ^^^^^ -> v:value
+ if value > 0:
+ # ^^^^^ -> v:value
+ var beta := value + 2
+ # | | ^^^^^ -> v:value
+ # ^^^^ v:if:beta -> v:if:beta
+ print(beta)
+ # ^^^^ -> v:if:beta
+
+ for counter in beta:
+ # | | ^^^^ -> v:if:beta
+ # ^^^^^^^ v:if:counter -> v:if:counter
+ print (counter)
+ # ^^^^^^^ -> v:if:counter
+
+ else:
+ for counter in value:
+ # | | ^^^^^ -> v:value
+ # ^^^^^^^ v:else:counter -> v:else:counter
+ print(counter)
+ # ^^^^^^^ -> v:else:counter
+
+func f():
+ var func1 = func(value): print(value + 13)
+ # | | | | ^^^^^ -> f:func1:value
+ # | | ^^^^^ f:func1:value -> f:func1:value
+ # ^^^^^ f:func1 -> f:func1
+ var func2 = func(value): print(value + 42)
+ # | | | | ^^^^^ -> f:func2:value
+ # | | ^^^^^ f:func2:value -> f:func2:value
+ # ^^^^^ f:func2 -> f:func2
+
+ func1.call(1)
+ #<^^^ -> f:func1
+ func2.call(2)
+ #<^^^ -> f:func2
+
+func m():
+ var value = 42
+ # ^^^^^ m:value -> m:value
+
+ match value:
+ # ^^^^^ -> m:value
+ 13:
+ print(value)
+ # ^^^^^ -> m:value
+ [var start, _, var end]:
+ # | | ^^^ m:match:array:end -> m:match:array:end
+ # ^^^^^ m:match:array:start -> m:match:array:start
+ print(start + end)
+ # | | ^^^ -> m:match:array:end
+ # ^^^^^ -> m:match:array:start
+ { "name": var name }:
+ # ^^^^ m:match:dict:var -> m:match:dict:var
+ print(name)
+ # ^^^^ -> m:match:dict:var
+ var whatever:
+ # ^^^^^^^^ m:match:var -> m:match:var
+ print(whatever)
+ # ^^^^^^^^ -> m:match:var
+
+func m2():
+ var value = 42
+ # ^^^^^ m2:value -> m2:value
+
+ match value:
+ # ^^^^^ -> m2:value
+ { "name": var name }:
+ # ^^^^ m2:match:dict:var -> m2:match:dict:var
+ print(name)
+ # ^^^^ -> m2:match:dict:var
+ [var name, ..]:
+ # ^^^^ m2:match:array:var -> m2:match:array:var
+ print(name)
+ # ^^^^ -> m2:match:array:var
+ var name:
+ # ^^^^ m2:match:var -> m2:match:var
+ print(name)
+ # ^^^^ -> m2:match:var
diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd
new file mode 100644
index 0000000000..338000fa0e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd
@@ -0,0 +1,56 @@
+extends Node
+
+var value := 42
+# ^^^^^ member:value -> member:value
+
+func variable():
+ var value = value + 42
+ #! | | ^^^^^ -> member:value
+ # ^^^^^ variable:value -> variable:value
+ print(value)
+ # ^^^^^ -> variable:value
+
+func array():
+ var value = [1,value,3,value+4]
+ #! | | | | ^^^^^ -> member:value
+ #! | | ^^^^^ -> member:value
+ # ^^^^^ array:value -> array:value
+ print(value)
+ # ^^^^^ -> array:value
+
+func dictionary():
+ var value = {
+ # ^^^^^ dictionary:value -> dictionary:value
+ "key1": value,
+ #! ^^^^^ -> member:value
+ "key2": 1 + value + 3,
+ #! ^^^^^ -> member:value
+ }
+ print(value)
+ # ^^^^^ -> dictionary:value
+
+func for_loop():
+ for value in value:
+ # | | ^^^^^ -> member:value
+ # ^^^^^ for:value -> for:value
+ print(value)
+ # ^^^^^ -> for:value
+
+func for_range():
+ for value in range(5, value):
+ # | | ^^^^^ -> member:value
+ # ^^^^^ for:range:value -> for:range:value
+ print(value)
+ # ^^^^^ -> for:range:value
+
+func matching():
+ match value:
+ # ^^^^^ -> member:value
+ 42: print(value)
+ # ^^^^^ -> member:value
+ [var value, ..]: print(value)
+ # | | ^^^^^ -> match:array:value
+ # ^^^^^ match:array:value -> match:array:value
+ var value: print(value)
+ # | | ^^^^^ -> match:var:value
+ # ^^^^^ match:var:value -> match:var:value
diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig
new file mode 100644
index 0000000000..fa43b3ad78
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/.editorconfig
@@ -0,0 +1,2 @@
+[*.{gd,out}]
+trim_trailing_whitespace = false
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
new file mode 100644
index 0000000000..e5eecbb819
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
new file mode 100644
index 0000000000..9168b69f86
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\\"")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
new file mode 100644
index 0000000000..37dc910e5f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
@@ -0,0 +1,3 @@
+func test():
+ # v
+ print(r"['"]*")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
new file mode 100644
index 0000000000..dcb5c2f289
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Closing "]" doesn't have an opening counterpart.
diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd
new file mode 100644
index 0000000000..d88b4a37c4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd
@@ -0,0 +1,5 @@
+func test():
+ var a = 0
+ match a:
+ 0 when a = 1:
+ print("assignment not allowed on pattern guard")
diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out
new file mode 100644
index 0000000000..e8f9130706
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Assignment is not allowed inside an expression.
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
index 7e1982597c..0c8a5d1367 100644
--- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
@@ -14,3 +14,7 @@ func test():
var TAU = "TAU"
print(TAU)
+
+ # New keyword for pattern guards.
+ var when = "when"
+ print(when)
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
index aae2ae13d5..8ac8e92ef7 100644
--- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
@@ -4,3 +4,4 @@ PI
INF
NAN
TAU
+when
diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd
index f04f4de08d..19f6e08285 100644
--- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd
+++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd
@@ -3,27 +3,32 @@ extends Node
func test():
var child = Node.new()
child.name = "Child"
+ @warning_ignore("unsafe_call_argument")
add_child(child)
child.owner = self
var hey = Node.new()
hey.name = "Hey"
+ @warning_ignore("unsafe_call_argument")
child.add_child(hey)
hey.owner = self
hey.unique_name_in_owner = true
var fake_hey = Node.new()
fake_hey.name = "Hey"
+ @warning_ignore("unsafe_call_argument")
add_child(fake_hey)
fake_hey.owner = self
var sub_child = Node.new()
sub_child.name = "SubChild"
+ @warning_ignore("unsafe_call_argument")
hey.add_child(sub_child)
sub_child.owner = self
var howdy = Node.new()
howdy.name = "Howdy"
+ @warning_ignore("unsafe_call_argument")
sub_child.add_child(howdy)
howdy.owner = self
howdy.unique_name_in_owner = true
diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd
index 8ba558e91d..3d9404b20b 100644
--- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd
+++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd
@@ -5,9 +5,11 @@ func test():
# Create the required node structure.
var hello = Node.new()
hello.name = "Hello"
+ @warning_ignore("unsafe_call_argument")
add_child(hello)
var world = Node.new()
world.name = "World"
+ @warning_ignore("unsafe_call_argument")
hello.add_child(world)
# All the ways of writing node paths below with the `$` operator are valid.
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
index df6001c7e2..f16c768f7f 100644
--- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
@@ -26,6 +26,7 @@ func test():
if true: (v as Callable).call()
print()
+ @warning_ignore("unsafe_call_argument")
other(v)
print()
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
new file mode 100644
index 0000000000..7ee2708999
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
@@ -0,0 +1,20 @@
+# Empty line:
+
+
+# Comment line:
+ # Comment.
+
+func test():
+ print(1)
+
+ if true:
+
+ # Empty line:
+
+
+ print(2)
+
+ # Comment line:
+ # Comment.
+
+ print(3)
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
new file mode 100644
index 0000000000..c40e402ba3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+1
+2
+3
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd
index 59cdc7d6c2..31de73813f 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd
+++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd
@@ -2,4 +2,5 @@ func foo(x):
return x + 1
func test():
+ @warning_ignore("unsafe_call_argument")
print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0)))))))))))))))))))))))))
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
new file mode 100644
index 0000000000..6f546f28be
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
@@ -0,0 +1,22 @@
+func test():
+ print(r"test ' \' \" \\ \n \t \u2023 test")
+ print(r"\n\\[\t ]*(\w+)")
+ print(r"")
+ print(r"\"")
+ print(r"\\\"")
+ print(r"\\")
+ print(r"\" \\\" \\\\\"")
+ print(r"\ \\ \\\ \\\\ \\\\\ \\")
+ print(r'"')
+ print(r'"(?:\\.|[^"])*"')
+ print(r"""""")
+ print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""")
+ print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''')
+ print(r"\t
+ \t")
+ print(r"\t \
+ \t")
+ print(r"""\t
+ \t""")
+ print(r"""\t \
+ \t""")
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out
new file mode 100644
index 0000000000..114ef0a6c3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out
@@ -0,0 +1,22 @@
+GDTEST_OK
+test ' \' \" \\ \n \t \u2023 test
+\n\\[\t ]*(\w+)
+
+\"
+\\\"
+\\
+\" \\\" \\\\\"
+\ \\ \\\ \\\\ \\\\\ \\
+"
+"(?:\\.|[^"])*"
+
+test \t "test"="" " \" \\\" \ \\ \\\ test
+r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""
+\t
+ \t
+\t \
+ \t
+\t
+ \t
+\t \
+ \t
diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd
index f5ae2a74a7..33accd92a9 100644
--- a/modules/gdscript/tests/scripts/parser/features/super.gd
+++ b/modules/gdscript/tests/scripts/parser/features/super.gd
@@ -36,6 +36,7 @@ class SayNothing extends Say:
print("howdy, see above")
func say(name):
+ @warning_ignore("unsafe_call_argument")
super(name + " super'd")
print(prefix, " say nothing... or not? ", name)
diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd
index 523959a016..20cc0cee2e 100644
--- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd
+++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd
@@ -29,6 +29,7 @@ func test():
const d = 1.1
_process(d)
+ @warning_ignore("unsafe_call_argument")
print(is_equal_approx(ㄥ, PI + (d * PI)))
func _process(Δ: float) -> void:
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
new file mode 100644
index 0000000000..cdc278ecf5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array = [Resource.new()]
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
new file mode 100644
index 0000000000..b7ef07afb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/for_loop_iterator_type_not_match_specified.gd
+>> 3
+>> Trying to assign value of type 'Resource' to a variable of type 'Node'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd
new file mode 100644
index 0000000000..f17fb9823d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd
@@ -0,0 +1,14 @@
+# GH-80157
+
+extends Node
+
+func f():
+ pass
+
+signal s()
+
+func test():
+ print(f)
+ print(s)
+ print(get_child)
+ print(ready)
diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out
new file mode 100644
index 0000000000..e5e9ff7043
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Node::f
+Node::[signal]s
+Node::get_child
+Node::[signal]ready
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
new file mode 100644
index 0000000000..bc899a3a6f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
@@ -0,0 +1,40 @@
+func test():
+ print("Test range.")
+ for e: float in range(2, 5):
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test int.")
+ for e: float in 3:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test untyped int array.")
+ var a1 := [10, 20, 30]
+ for e: float in a1:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test typed int array.")
+ var a2: Array[int] = [10, 20, 30]
+ for e: float in a2:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ # GH-82021
+ print("Test implicitly typed array literal.")
+ for e: float in [100, 200, 300]:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test String-keys dictionary.")
+ var d1 := {a = 1, b = 2, c = 3}
+ for k: StringName in d1:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
+
+ print("Test RefCounted-keys dictionary.")
+ var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
+ for k: RefCounted in d2:
+ var key := k
+ prints(k.get_class(), key.get_class())
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
new file mode 100644
index 0000000000..eeebdc4be5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
@@ -0,0 +1,29 @@
+GDTEST_OK
+Test range.
+2.0 2.0
+3.0 3.0
+4.0 4.0
+Test int.
+0.0 0.0
+1.0 1.0
+2.0 2.0
+Test untyped int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test typed int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test implicitly typed array literal.
+100.0 100.0
+200.0 200.0
+300.0 300.0
+Test String-keys dictionary.
+&"a" &"a"
+&"b" &"b"
+&"c" &"c"
+Test RefCounted-keys dictionary.
+RefCounted RefCounted
+Resource Resource
+ConfigFile ConfigFile
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
new file mode 100644
index 0000000000..4cb51f8512
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
@@ -0,0 +1,71 @@
+var global := 0
+
+func test():
+ var a = 0
+ var b = 1
+
+ match a:
+ 0 when b == 0:
+ print("does not run" if true else "")
+ 0 when b == 1:
+ print("guards work")
+ _:
+ print("does not run")
+
+ match a:
+ var a_bind when b == 0:
+ prints("a is", a_bind, "and b is 0")
+ var a_bind when b == 1:
+ prints("a is", a_bind, "and b is 1")
+ _:
+ print("does not run")
+
+ match a:
+ var a_bind when a_bind < 0:
+ print("a is less than zero")
+ var a_bind when a_bind == 0:
+ print("a is equal to zero")
+ _:
+ print("a is more than zero")
+
+ match [1, 2, 3]:
+ [1, 2, var element] when element == 0:
+ print("does not run")
+ [1, 2, var element] when element == 3:
+ print("3rd element is 3")
+
+ match a:
+ _ when b == 0:
+ print("does not run")
+ _ when b == 1:
+ print("works with wildcard too.")
+ _:
+ print("does not run")
+
+ match a:
+ 0, 1 when b == 0:
+ print("does not run")
+ 0, 1 when b == 1:
+ print("guard with multiple patterns")
+ _:
+ print("does not run")
+
+ match a:
+ 0 when b == 0:
+ print("does not run")
+ 0:
+ print("regular pattern after guard mismatch")
+
+ match a:
+ 1 when side_effect():
+ print("should not run the side effect call")
+ 0 when side_effect():
+ print("will run the side effect call, but not this")
+ _:
+ assert(global == 1)
+ print("side effect only ran once")
+
+func side_effect():
+ print("side effect")
+ global += 1
+ return false
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out
new file mode 100644
index 0000000000..452d1ff4bf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+guards work
+a is 0 and b is 1
+a is equal to zero
+3rd element is 3
+works with wildcard too.
+guard with multiple patterns
+regular pattern after guard mismatch
+side effect
+side effect only ran once
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
new file mode 100644
index 0000000000..805ea42455
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -0,0 +1,72 @@
+class_name TestMemberInfo
+
+class MyClass:
+ pass
+
+enum MyEnum {}
+
+const Utils = preload("../../utils.notest.gd")
+
+static var test_static_var_untyped
+static var test_static_var_weak_null = null
+static var test_static_var_weak_int = 1
+static var test_static_var_hard_int: int
+
+var test_var_untyped
+var test_var_weak_null = null
+var test_var_weak_int = 1
+@export var test_var_weak_int_exported = 1
+var test_var_weak_variant_type = TYPE_NIL
+@export var test_var_weak_variant_type_exported = TYPE_NIL
+var test_var_hard_variant: Variant
+var test_var_hard_int: int
+var test_var_hard_variant_type: Variant.Type
+@export var test_var_hard_variant_type_exported: Variant.Type
+var test_var_hard_node_process_mode: Node.ProcessMode
+var test_var_hard_my_enum: MyEnum
+var test_var_hard_array: Array
+var test_var_hard_array_int: Array[int]
+var test_var_hard_array_variant_type: Array[Variant.Type]
+var test_var_hard_array_node_process_mode: Array[Node.ProcessMode]
+var test_var_hard_array_my_enum: Array[MyEnum]
+var test_var_hard_array_resource: Array[Resource]
+var test_var_hard_array_this: Array[TestMemberInfo]
+var test_var_hard_array_my_class: Array[MyClass]
+var test_var_hard_resource: Resource
+var test_var_hard_this: TestMemberInfo
+var test_var_hard_my_class: MyClass
+
+static func test_static_func(): pass
+
+func test_func_implicit_void(): pass
+func test_func_explicit_void() -> void: pass
+func test_func_weak_null(): return null
+func test_func_weak_int(): return 1
+func test_func_hard_variant() -> Variant: return null
+func test_func_hard_int() -> int: return 1
+func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass
+func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
+
+signal test_signal_1()
+signal test_signal_2(a: Variant, b)
+signal test_signal_3(a: int, b: Array[int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
+signal test_signal_5(a: MyEnum, b: Array[MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
+signal test_signal_8(a: MyClass, b: Array[MyClass])
+
+func test():
+ var script: Script = get_script()
+ for property in script.get_property_list():
+ if str(property.name).begins_with("test_"):
+ print(Utils.get_property_signature(property, true))
+ for property in get_property_list():
+ if str(property.name).begins_with("test_"):
+ print(Utils.get_property_signature(property))
+ for method in get_method_list():
+ if str(method.name).begins_with("test_"):
+ print(Utils.get_method_signature(method))
+ for method in get_signal_list():
+ if str(method.name).begins_with("test_"):
+ print(Utils.get_method_signature(method, true))
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out
new file mode 100644
index 0000000000..3a91507da9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out
@@ -0,0 +1,45 @@
+GDTEST_OK
+static var test_static_var_untyped: Variant
+static var test_static_var_weak_null: Variant
+static var test_static_var_weak_int: Variant
+static var test_static_var_hard_int: int
+var test_var_untyped: Variant
+var test_var_weak_null: Variant
+var test_var_weak_int: Variant
+@export var test_var_weak_int_exported: int
+var test_var_weak_variant_type: Variant
+@export var test_var_weak_variant_type_exported: Variant.Type
+var test_var_hard_variant: Variant
+var test_var_hard_int: int
+var test_var_hard_variant_type: Variant.Type
+@export var test_var_hard_variant_type_exported: Variant.Type
+var test_var_hard_node_process_mode: Node.ProcessMode
+var test_var_hard_my_enum: TestMemberInfo.MyEnum
+var test_var_hard_array: Array
+var test_var_hard_array_int: Array[int]
+var test_var_hard_array_variant_type: Array[Variant.Type]
+var test_var_hard_array_node_process_mode: Array[Node.ProcessMode]
+var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum]
+var test_var_hard_array_resource: Array[Resource]
+var test_var_hard_array_this: Array[TestMemberInfo]
+var test_var_hard_array_my_class: Array[RefCounted]
+var test_var_hard_resource: Resource
+var test_var_hard_this: TestMemberInfo
+var test_var_hard_my_class: RefCounted
+static func test_static_func() -> void
+func test_func_implicit_void() -> void
+func test_func_explicit_void() -> void
+func test_func_weak_null() -> Variant
+func test_func_weak_int() -> Variant
+func test_func_hard_variant() -> Variant
+func test_func_hard_int() -> int
+func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void
+func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void
+signal test_signal_1()
+signal test_signal_2(a: Variant, b: Variant)
+signal test_signal_3(a: int, b: Array[int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
+signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
+signal test_signal_8(a: RefCounted, b: Array[RefCounted])
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
new file mode 100644
index 0000000000..d0cbeeab85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
@@ -0,0 +1,45 @@
+# GH-82169
+
+const Utils = preload("../../utils.notest.gd")
+
+class A:
+ static var test_static_var_a1
+ static var test_static_var_a2
+ var test_var_a1
+ var test_var_a2
+ static func test_static_func_a1(): pass
+ static func test_static_func_a2(): pass
+ func test_func_a1(): pass
+ func test_func_a2(): pass
+ signal test_signal_a1()
+ signal test_signal_a2()
+
+class B extends A:
+ static var test_static_var_b1
+ static var test_static_var_b2
+ var test_var_b1
+ var test_var_b2
+ static func test_static_func_b1(): pass
+ static func test_static_func_b2(): pass
+ func test_func_b1(): pass
+ func test_func_b2(): pass
+ signal test_signal_b1()
+ signal test_signal_b2()
+
+func test():
+ var b := B.new()
+ for property in (B as GDScript).get_property_list():
+ if str(property.name).begins_with("test_"):
+ print(Utils.get_property_signature(property, true))
+ print("---")
+ for property in b.get_property_list():
+ if str(property.name).begins_with("test_"):
+ print(Utils.get_property_signature(property))
+ print("---")
+ for method in b.get_method_list():
+ if str(method.name).begins_with("test_"):
+ print(Utils.get_method_signature(method))
+ print("---")
+ for method in b.get_signal_list():
+ if str(method.name).begins_with("test_"):
+ print(Utils.get_method_signature(method, true))
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out
new file mode 100644
index 0000000000..f294b66ef9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out
@@ -0,0 +1,24 @@
+GDTEST_OK
+static var test_static_var_a1: Variant
+static var test_static_var_a2: Variant
+static var test_static_var_b1: Variant
+static var test_static_var_b2: Variant
+---
+var test_var_b1: Variant
+var test_var_b2: Variant
+var test_var_a1: Variant
+var test_var_a2: Variant
+---
+static func test_static_func_b1() -> void
+static func test_static_func_b2() -> void
+func test_func_b1() -> void
+func test_func_b2() -> void
+static func test_static_func_a1() -> void
+static func test_static_func_a2() -> void
+func test_func_a1() -> void
+func test_func_a2() -> void
+---
+signal test_signal_b1()
+signal test_signal_b2()
+signal test_signal_a1()
+signal test_signal_a2()
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
new file mode 100644
index 0000000000..6c5df32ffe
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
@@ -0,0 +1,36 @@
+class MyClass:
+ const TEST = 10
+
+enum MyEnum {A, B, C}
+
+const Utils = preload("../../utils.notest.gd")
+const Other = preload("./metatypes.notest.gd")
+
+var test_native := JSON
+var test_script := Other
+var test_class := MyClass
+var test_enum := MyEnum
+
+func check_gdscript_native_class(value: Variant) -> void:
+ print(var_to_str(value).get_slice(",", 0).trim_prefix("Object("))
+
+func check_gdscript(value: GDScript) -> void:
+ print(value.get_class())
+
+func check_enum(value: Dictionary) -> void:
+ print(value)
+
+func test():
+ for property in get_property_list():
+ if str(property.name).begins_with("test_"):
+ print(Utils.get_property_signature(property))
+
+ check_gdscript_native_class(test_native)
+ check_gdscript(test_script)
+ check_gdscript(test_class)
+ check_enum(test_enum)
+
+ print(test_native.stringify([]))
+ print(test_script.TEST)
+ print(test_class.TEST)
+ print(test_enum.keys())
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd
new file mode 100644
index 0000000000..e6a591b927
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd
@@ -0,0 +1 @@
+const TEST = 100
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.out b/modules/gdscript/tests/scripts/runtime/features/metatypes.out
new file mode 100644
index 0000000000..352d1caa59
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.out
@@ -0,0 +1,13 @@
+GDTEST_OK
+var test_native: GDScriptNativeClass
+var test_script: GDScript
+var test_class: GDScript
+var test_enum: Dictionary
+GDScriptNativeClass
+GDScript
+GDScript
+{ "A": 0, "B": 1, "C": 2 }
+[]
+100
+10
+["A", "B", "C"]
diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd
new file mode 100644
index 0000000000..b875efef56
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd
@@ -0,0 +1,6 @@
+# GH-73213
+
+func test():
+ var object := Object.new() # Not `Object()`.
+ print(object.get_class())
+ object.free()
diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out
new file mode 100644
index 0000000000..3673881576
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Object
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
index 2f55059334..fd1460a48f 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
@@ -12,6 +12,7 @@ func test():
print("end")
func test_construct(v, f):
+ @warning_ignore("unsafe_call_argument")
Vector2(v, v) # Built-in type construct.
assert(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
index 8da8bb7e53..7fa76ca4b0 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
@@ -44,6 +44,7 @@ func test():
@warning_ignore("unsafe_method_access")
var path = get_script().get_path().get_base_dir()
+ @warning_ignore("unsafe_call_argument")
var other = load(path + "/static_variables_load.gd")
prints("load.perm:", other.perm)
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
index fead2df854..1e66d8f34a 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
@@ -24,7 +24,8 @@ func test():
print(StringName("hello"))
print(NodePath("hello/world"))
var node := Node.new()
- print(RID(node))
+ @warning_ignore("unsafe_call_argument")
+ print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented?
print(node.get_name)
print(node.property_list_changed)
node.free()
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
new file mode 100644
index 0000000000..fb20817117
--- /dev/null
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -0,0 +1,281 @@
+static func get_type(property: Dictionary, is_return: bool = false) -> String:
+ match property.type:
+ TYPE_NIL:
+ if property.usage & PROPERTY_USAGE_NIL_IS_VARIANT:
+ return "Variant"
+ return "void" if is_return else "null"
+ TYPE_INT:
+ if property.usage & PROPERTY_USAGE_CLASS_IS_ENUM:
+ if property.class_name == &"":
+ return "<unknown enum>"
+ return property.class_name
+ TYPE_ARRAY:
+ if property.hint == PROPERTY_HINT_ARRAY_TYPE:
+ if str(property.hint_string).is_empty():
+ return "Array[<unknown type>]"
+ return "Array[%s]" % property.hint_string
+ TYPE_OBJECT:
+ if not str(property.class_name).is_empty():
+ return property.class_name
+ return variant_get_type_name(property.type)
+
+
+static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
+ var result: String = ""
+ if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
+ printerr("Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.")
+ if property.usage & PROPERTY_USAGE_DEFAULT:
+ result += "@export "
+ if is_static:
+ result += "static "
+ result += "var " + property.name + ": " + get_type(property)
+ return result
+
+
+static func get_property_additional_info(property: Dictionary) -> String:
+ return 'hint=%s hint_string="%s" usage=%s' % [
+ get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
+ str(property.hint_string).c_escape(),
+ get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
+ ]
+
+
+static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
+ var result: String = ""
+ if method.flags & METHOD_FLAG_STATIC:
+ result += "static "
+ result += ("signal " if is_signal else "func ") + method.name + "("
+
+ var args: Array[Dictionary] = method.args
+ var default_args: Array = method.default_args
+ var mandatory_argc: int = args.size() - default_args.size()
+ for i in args.size():
+ if i > 0:
+ result += ", "
+ var arg: Dictionary = args[i]
+ result += arg.name + ": " + get_type(arg)
+ if i >= mandatory_argc:
+ result += " = " + var_to_str(default_args[i - mandatory_argc])
+
+ result += ")"
+ if is_signal:
+ if get_type(method.return, true) != "void":
+ printerr("Signal return type must be `void`.")
+ else:
+ result += " -> " + get_type(method.return, true)
+ return result
+
+
+static func variant_get_type_name(type: Variant.Type) -> String:
+ match type:
+ TYPE_NIL:
+ return "Nil" # `Nil` in core, `null` in GDScript.
+ TYPE_BOOL:
+ return "bool"
+ TYPE_INT:
+ return "int"
+ TYPE_FLOAT:
+ return "float"
+ TYPE_STRING:
+ return "String"
+ TYPE_VECTOR2:
+ return "Vector2"
+ TYPE_VECTOR2I:
+ return "Vector2i"
+ TYPE_RECT2:
+ return "Rect2"
+ TYPE_RECT2I:
+ return "Rect2i"
+ TYPE_VECTOR3:
+ return "Vector3"
+ TYPE_VECTOR3I:
+ return "Vector3i"
+ TYPE_TRANSFORM2D:
+ return "Transform2D"
+ TYPE_VECTOR4:
+ return "Vector4"
+ TYPE_VECTOR4I:
+ return "Vector4i"
+ TYPE_PLANE:
+ return "Plane"
+ TYPE_QUATERNION:
+ return "Quaternion"
+ TYPE_AABB:
+ return "AABB"
+ TYPE_BASIS:
+ return "Basis"
+ TYPE_TRANSFORM3D:
+ return "Transform3D"
+ TYPE_PROJECTION:
+ return "Projection"
+ TYPE_COLOR:
+ return "Color"
+ TYPE_STRING_NAME:
+ return "StringName"
+ TYPE_NODE_PATH:
+ return "NodePath"
+ TYPE_RID:
+ return "RID"
+ TYPE_OBJECT:
+ return "Object"
+ TYPE_CALLABLE:
+ return "Callable"
+ TYPE_SIGNAL:
+ return "Signal"
+ TYPE_DICTIONARY:
+ return "Dictionary"
+ TYPE_ARRAY:
+ return "Array"
+ TYPE_PACKED_BYTE_ARRAY:
+ return "PackedByteArray"
+ TYPE_PACKED_INT32_ARRAY:
+ return "PackedInt32Array"
+ TYPE_PACKED_INT64_ARRAY:
+ return "PackedInt64Array"
+ TYPE_PACKED_FLOAT32_ARRAY:
+ return "PackedFloat32Array"
+ TYPE_PACKED_FLOAT64_ARRAY:
+ return "PackedFloat64Array"
+ TYPE_PACKED_STRING_ARRAY:
+ return "PackedStringArray"
+ TYPE_PACKED_VECTOR2_ARRAY:
+ return "PackedVector2Array"
+ TYPE_PACKED_VECTOR3_ARRAY:
+ return "PackedVector3Array"
+ TYPE_PACKED_COLOR_ARRAY:
+ return "PackedColorArray"
+ push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
+ return "<invalid type>"
+
+
+static func get_property_hint_name(hint: PropertyHint) -> String:
+ match hint:
+ PROPERTY_HINT_NONE:
+ return "PROPERTY_HINT_NONE"
+ PROPERTY_HINT_RANGE:
+ return "PROPERTY_HINT_RANGE"
+ PROPERTY_HINT_ENUM:
+ return "PROPERTY_HINT_ENUM"
+ PROPERTY_HINT_ENUM_SUGGESTION:
+ return "PROPERTY_HINT_ENUM_SUGGESTION"
+ PROPERTY_HINT_EXP_EASING:
+ return "PROPERTY_HINT_EXP_EASING"
+ PROPERTY_HINT_LINK:
+ return "PROPERTY_HINT_LINK"
+ PROPERTY_HINT_FLAGS:
+ return "PROPERTY_HINT_FLAGS"
+ PROPERTY_HINT_LAYERS_2D_RENDER:
+ return "PROPERTY_HINT_LAYERS_2D_RENDER"
+ PROPERTY_HINT_LAYERS_2D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_2D_PHYSICS"
+ PROPERTY_HINT_LAYERS_2D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_2D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_3D_RENDER:
+ return "PROPERTY_HINT_LAYERS_3D_RENDER"
+ PROPERTY_HINT_LAYERS_3D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_3D_PHYSICS"
+ PROPERTY_HINT_LAYERS_3D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_3D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_AVOIDANCE:
+ return "PROPERTY_HINT_LAYERS_AVOIDANCE"
+ PROPERTY_HINT_FILE:
+ return "PROPERTY_HINT_FILE"
+ PROPERTY_HINT_DIR:
+ return "PROPERTY_HINT_DIR"
+ PROPERTY_HINT_GLOBAL_FILE:
+ return "PROPERTY_HINT_GLOBAL_FILE"
+ PROPERTY_HINT_GLOBAL_DIR:
+ return "PROPERTY_HINT_GLOBAL_DIR"
+ PROPERTY_HINT_RESOURCE_TYPE:
+ return "PROPERTY_HINT_RESOURCE_TYPE"
+ PROPERTY_HINT_MULTILINE_TEXT:
+ return "PROPERTY_HINT_MULTILINE_TEXT"
+ PROPERTY_HINT_EXPRESSION:
+ return "PROPERTY_HINT_EXPRESSION"
+ PROPERTY_HINT_PLACEHOLDER_TEXT:
+ return "PROPERTY_HINT_PLACEHOLDER_TEXT"
+ PROPERTY_HINT_COLOR_NO_ALPHA:
+ return "PROPERTY_HINT_COLOR_NO_ALPHA"
+ PROPERTY_HINT_OBJECT_ID:
+ return "PROPERTY_HINT_OBJECT_ID"
+ PROPERTY_HINT_TYPE_STRING:
+ return "PROPERTY_HINT_TYPE_STRING"
+ PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE:
+ return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE"
+ PROPERTY_HINT_OBJECT_TOO_BIG:
+ return "PROPERTY_HINT_OBJECT_TOO_BIG"
+ PROPERTY_HINT_NODE_PATH_VALID_TYPES:
+ return "PROPERTY_HINT_NODE_PATH_VALID_TYPES"
+ PROPERTY_HINT_SAVE_FILE:
+ return "PROPERTY_HINT_SAVE_FILE"
+ PROPERTY_HINT_GLOBAL_SAVE_FILE:
+ return "PROPERTY_HINT_GLOBAL_SAVE_FILE"
+ PROPERTY_HINT_INT_IS_OBJECTID:
+ return "PROPERTY_HINT_INT_IS_OBJECTID"
+ PROPERTY_HINT_INT_IS_POINTER:
+ return "PROPERTY_HINT_INT_IS_POINTER"
+ PROPERTY_HINT_ARRAY_TYPE:
+ return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_LOCALE_ID:
+ return "PROPERTY_HINT_LOCALE_ID"
+ PROPERTY_HINT_LOCALIZABLE_STRING:
+ return "PROPERTY_HINT_LOCALIZABLE_STRING"
+ PROPERTY_HINT_NODE_TYPE:
+ return "PROPERTY_HINT_NODE_TYPE"
+ PROPERTY_HINT_HIDE_QUATERNION_EDIT:
+ return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
+ PROPERTY_HINT_PASSWORD:
+ return "PROPERTY_HINT_PASSWORD"
+ push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ return "<invalid hint>"
+
+
+static func get_property_usage_string(usage: int) -> String:
+ if usage == PROPERTY_USAGE_NONE:
+ return "PROPERTY_USAGE_NONE"
+
+ const FLAGS: Array[Array] = [
+ [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"],
+ [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"],
+ [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"],
+ [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"],
+ [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"],
+ [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"],
+ [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"],
+ [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"],
+ [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"],
+ [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"],
+ [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"],
+ [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"],
+ [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"],
+ [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"],
+ [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"],
+ [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"],
+ [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"],
+ [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"],
+ [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"],
+ [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"],
+ [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"],
+ [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"],
+ [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"],
+ [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"],
+ [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"],
+ [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"],
+ [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"],
+ [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"],
+ [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"],
+ [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"],
+ ]
+
+ var result: String = ""
+
+ for flag in FLAGS:
+ if usage & flag[0]:
+ result += flag[1] + "|"
+ usage &= ~flag[0]
+
+ if usage != PROPERTY_USAGE_NONE:
+ push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ return "<invalid usage flags>"
+
+ return result.left(-1)
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 0446a7aad6..b86a8b3cb1 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -138,12 +138,13 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const
for (const KeyValue<StringName, GDScriptFunction *> &E : script->get_member_functions()) {
const GDScriptFunction *func = E.value;
- String signature = "Disassembling " + func->get_name().operator String() + "(";
- for (int i = 0; i < func->get_argument_count(); i++) {
+ const MethodInfo &mi = func->get_method_info();
+ String signature = "Disassembling " + mi.name + "(";
+ for (int i = 0; i < mi.arguments.size(); i++) {
if (i > 0) {
signature += ", ";
}
- signature += func->get_argument_name(i);
+ signature += mi.arguments[i].name;
}
print_line(signature + ")");
#ifdef TOOLS_ENABLED
@@ -156,7 +157,7 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const
for (const KeyValue<StringName, Ref<GDScript>> &F : script->get_subclasses()) {
const Ref<GDScript> inner_script = F.value;
print_line("");
- print_line(vformat("Inner Class: %s", inner_script->get_script_class_name()));
+ print_line(vformat("Inner Class: %s", inner_script->get_local_name()));
print_line("");
recursively_disassemble_functions(inner_script, p_lines);
}
diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h
new file mode 100644
index 0000000000..e57df00e2d
--- /dev/null
+++ b/modules/gdscript/tests/test_lsp.h
@@ -0,0 +1,480 @@
+/**************************************************************************/
+/* test_lsp.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_LSP_H
+#define TEST_LSP_H
+
+#ifdef TOOLS_ENABLED
+
+#include "tests/test_macros.h"
+
+#include "../language_server/gdscript_extend_parser.h"
+#include "../language_server/gdscript_language_protocol.h"
+#include "../language_server/gdscript_workspace.h"
+#include "../language_server/godot_lsp.h"
+
+#include "core/io/dir_access.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/os.h"
+#include "editor/editor_help.h"
+#include "editor/editor_node.h"
+#include "modules/gdscript/gdscript_analyzer.h"
+#include "modules/regex/regex.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+template <>
+struct doctest::StringMaker<lsp::Position> {
+ static doctest::String convert(const lsp::Position &p_val) {
+ return p_val.to_string().utf8().get_data();
+ }
+};
+
+template <>
+struct doctest::StringMaker<lsp::Range> {
+ static doctest::String convert(const lsp::Range &p_val) {
+ return p_val.to_string().utf8().get_data();
+ }
+};
+
+template <>
+struct doctest::StringMaker<GodotPosition> {
+ static doctest::String convert(const GodotPosition &p_val) {
+ return p_val.to_string().utf8().get_data();
+ }
+};
+
+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`.
+const String root = "modules/gdscript/tests/scripts/";
+
+/*
+ * After use:
+ * * `memdelete` returned `GDScriptLanguageProtocol`.
+ * * Call `GDScriptTests::::finish_language`.
+ */
+GDScriptLanguageProtocol *initialize(const String &p_root) {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(p_root, &err));
+ REQUIRE_MESSAGE(err == OK, "Could not open specified root directory");
+ String absolute_root = dir->get_current_dir();
+ init_language(absolute_root);
+
+ GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol);
+
+ Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
+ workspace->root = absolute_root;
+ // On windows: `C:/...` -> `C%3A/...`.
+ workspace->root_uri = "file:///" + absolute_root.lstrip("/").replace_first(":", "%3A");
+
+ return proto;
+}
+
+lsp::Position pos(const int p_line, const int p_character) {
+ lsp::Position p;
+ p.line = p_line;
+ p.character = p_character;
+ return p;
+}
+
+lsp::Range range(const lsp::Position p_start, const lsp::Position p_end) {
+ lsp::Range r;
+ r.start = p_start;
+ r.end = p_end;
+ return r;
+}
+
+lsp::TextDocumentPositionParams pos_in(const lsp::DocumentUri &p_uri, const lsp::Position p_pos) {
+ lsp::TextDocumentPositionParams params;
+ params.textDocument.uri = p_uri;
+ params.position = p_pos;
+ return params;
+}
+
+const lsp::DocumentSymbol *test_resolve_symbol_at(const String &p_uri, const lsp::Position p_pos, const String &p_expected_uri, const String &p_expected_name, const lsp::Range &p_expected_range) {
+ Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
+
+ lsp::TextDocumentPositionParams params = pos_in(p_uri, p_pos);
+ const lsp::DocumentSymbol *symbol = workspace->resolve_symbol(params);
+ CHECK(symbol);
+
+ if (symbol) {
+ CHECK_EQ(symbol->uri, p_expected_uri);
+ CHECK_EQ(symbol->name, p_expected_name);
+ CHECK_EQ(symbol->selectionRange, p_expected_range);
+ }
+
+ return symbol;
+}
+
+struct InlineTestData {
+ lsp::Range range;
+ String text;
+ String name;
+ String ref;
+
+ static bool try_parse(const Vector<String> &p_lines, const int p_line_number, InlineTestData &r_data) {
+ String line = p_lines[p_line_number];
+
+ RegEx regex = RegEx("^\\t*#[ |]*(?<range>(?<left><)?\\^+)(\\s+(?<name>(?!->)\\S+))?(\\s+->\\s+(?<ref>\\S+))?");
+ Ref<RegExMatch> match = regex.search(line);
+ if (match.is_null()) {
+ return false;
+ }
+
+ // Find first line without leading comment above current line.
+ int target_line = p_line_number;
+ while (target_line >= 0) {
+ String dedented = p_lines[target_line].lstrip("\t");
+ if (!dedented.begins_with("#")) {
+ break;
+ }
+ target_line--;
+ }
+ if (target_line < 0) {
+ return false;
+ }
+ r_data.range.start.line = r_data.range.end.line = target_line;
+
+ String marker = match->get_string("range");
+ int i = line.find(marker);
+ REQUIRE(i >= 0);
+ r_data.range.start.character = i;
+ if (!match->get_string("left").is_empty()) {
+ // Include `#` (comment char) in range.
+ r_data.range.start.character--;
+ }
+ r_data.range.end.character = i + marker.length();
+
+ String target = p_lines[target_line];
+ r_data.text = target.substr(r_data.range.start.character, r_data.range.end.character - r_data.range.start.character);
+
+ r_data.name = match->get_string("name");
+ r_data.ref = match->get_string("ref");
+
+ return true;
+ }
+};
+
+Vector<InlineTestData> read_tests(const String &p_path) {
+ Error err;
+ String source = FileAccess::get_file_as_string(p_path, &err);
+ REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path));
+
+ // Format:
+ // ```gdscript
+ // var foo = bar + baz
+ // # | | | | ^^^ name -> ref
+ // # | | ^^^ -> ref
+ // # ^^^ name
+ //
+ // func my_func():
+ // # ^^^^^^^ name
+ // var value = foo + 42
+ // # ^^^^^ name
+ // print(value)
+ // # ^^^^^ -> ref
+ // ```
+ //
+ // * `^`: Range marker.
+ // * `name`: Unique name. Can contain any characters except whitespace chars.
+ // * `ref`: Reference to unique name.
+ //
+ // Notes:
+ // * If range should include first content-char (which is occupied by `#`): use `<` for next marker.
+ // -> Range expands 1 to left (-> includes `#`).
+ // * Note: Means: Range cannot be single char directly marked by `#`, but must be at least two chars (marked with `#<`).
+ // * Comment must start at same ident as line its marked (-> because of tab alignment...).
+ // * Use spaces to align after `#`! -> for correct alignment
+ // * Between `#` and `^` can be spaces or `|` (to better visualize what's marked below).
+ PackedStringArray lines = source.split("\n");
+
+ PackedStringArray names;
+ Vector<InlineTestData> data;
+ for (int i = 0; i < lines.size(); i++) {
+ InlineTestData d;
+ if (InlineTestData::try_parse(lines, i, d)) {
+ if (!d.name.is_empty()) {
+ // Safety check: names must be unique.
+ if (names.find(d.name) != -1) {
+ FAIL(vformat("Duplicated name '%s' in '%s'. Names must be unique!", d.name, p_path));
+ }
+ names.append(d.name);
+ }
+
+ data.append(d);
+ }
+ }
+
+ return data;
+}
+
+void test_resolve_symbol(const String &p_uri, const InlineTestData &p_test_data, const Vector<InlineTestData> &p_all_data) {
+ if (p_test_data.ref.is_empty()) {
+ return;
+ }
+
+ SUBCASE(vformat("Can resolve symbol '%s' at %s to '%s'", p_test_data.text, p_test_data.range.to_string(), p_test_data.ref).utf8().get_data()) {
+ const InlineTestData *target = nullptr;
+ for (int i = 0; i < p_all_data.size(); i++) {
+ if (p_all_data[i].name == p_test_data.ref) {
+ target = &p_all_data[i];
+ break;
+ }
+ }
+ REQUIRE_MESSAGE(target, vformat("No target for ref '%s'", p_test_data.ref));
+
+ Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
+ lsp::Position pos = p_test_data.range.start;
+
+ SUBCASE("start of identifier") {
+ pos.character = p_test_data.range.start.character;
+ test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range);
+ }
+
+ SUBCASE("inside identifier") {
+ pos.character = (p_test_data.range.end.character + p_test_data.range.start.character) / 2;
+ test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range);
+ }
+
+ SUBCASE("end of identifier") {
+ pos.character = p_test_data.range.end.character;
+ test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range);
+ }
+ }
+}
+
+Vector<InlineTestData> filter_ref_towards(const Vector<InlineTestData> &p_data, const String &p_name) {
+ Vector<InlineTestData> res;
+
+ for (const InlineTestData &d : p_data) {
+ if (d.ref == p_name) {
+ res.append(d);
+ }
+ }
+
+ return res;
+}
+
+void test_resolve_symbols(const String &p_uri, const Vector<InlineTestData> &p_test_data, const Vector<InlineTestData> &p_all_data) {
+ for (const InlineTestData &d : p_test_data) {
+ test_resolve_symbol(p_uri, d, p_all_data);
+ }
+}
+
+void assert_no_errors_in(const String &p_path) {
+ Error err;
+ String source = FileAccess::get_file_as_string(p_path, &err);
+ REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path));
+
+ GDScriptParser parser;
+ err = parser.parse(source, p_path, true);
+ REQUIRE_MESSAGE(err == OK, vformat("Errors while parsing '%s'", p_path));
+
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.analyze();
+ REQUIRE_MESSAGE(err == OK, vformat("Errors while analyzing '%s'", p_path));
+}
+
+inline lsp::Position lsp_pos(int line, int character) {
+ lsp::Position p;
+ p.line = line;
+ p.character = character;
+ return p;
+}
+
+void test_position_roundtrip(lsp::Position p_lsp, GodotPosition p_gd, const PackedStringArray &p_lines) {
+ GodotPosition actual_gd = GodotPosition::from_lsp(p_lsp, p_lines);
+ CHECK_EQ(p_gd, actual_gd);
+ lsp::Position actual_lsp = p_gd.to_lsp(p_lines);
+ CHECK_EQ(p_lsp, actual_lsp);
+}
+
+// Note:
+// * Cursor is BETWEEN chars
+// * `va|r` -> cursor between `a`&`r`
+// * `var`
+// ^
+// -> Character on `r` -> cursor between `a`&`r`s for tests:
+// * Line & Char:
+// * LSP: both 0-based
+// * Godot: both 1-based
+TEST_SUITE("[Modules][GDScript][LSP]") {
+ TEST_CASE("Can convert positions to and from Godot") {
+ String code = R"(extends Node
+
+var member := 42
+
+func f():
+ var value := 42
+ return value + member)";
+ PackedStringArray lines = code.split("\n");
+
+ SUBCASE("line after end") {
+ lsp::Position lsp = lsp_pos(7, 0);
+ GodotPosition gd(8, 1);
+ test_position_roundtrip(lsp, gd, lines);
+ }
+ SUBCASE("first char in first line") {
+ lsp::Position lsp = lsp_pos(0, 0);
+ GodotPosition gd(1, 1);
+ test_position_roundtrip(lsp, gd, lines);
+ }
+
+ SUBCASE("with tabs") {
+ // On `v` in `value` in `var value := ...`.
+ lsp::Position lsp = lsp_pos(5, 6);
+ GodotPosition gd(6, 13);
+ test_position_roundtrip(lsp, gd, lines);
+ }
+
+ SUBCASE("doesn't fail with column outside of character length") {
+ lsp::Position lsp = lsp_pos(2, 100);
+ GodotPosition::from_lsp(lsp, lines);
+
+ GodotPosition gd(3, 100);
+ gd.to_lsp(lines);
+ }
+
+ SUBCASE("doesn't fail with line outside of line length") {
+ lsp::Position lsp = lsp_pos(200, 100);
+ GodotPosition::from_lsp(lsp, lines);
+
+ GodotPosition gd(300, 100);
+ gd.to_lsp(lines);
+ }
+
+ SUBCASE("special case: negative line for root class") {
+ GodotPosition gd(-1, 0);
+ lsp::Position expected = lsp_pos(0, 0);
+ lsp::Position actual = gd.to_lsp(lines);
+ CHECK_EQ(actual, expected);
+ }
+ SUBCASE("special case: lines.length() + 1 for root class") {
+ GodotPosition gd(lines.size() + 1, 0);
+ lsp::Position expected = lsp_pos(lines.size(), 0);
+ lsp::Position actual = gd.to_lsp(lines);
+ CHECK_EQ(actual, expected);
+ }
+ }
+ TEST_CASE("[workspace][resolve_symbol]") {
+ GDScriptLanguageProtocol *proto = initialize(root);
+ REQUIRE(proto);
+ Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
+
+ {
+ String path = "res://lsp/local_variables.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ SUBCASE("Can get correct ranges for public variables") {
+ Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "member");
+ test_resolve_symbols(uri, test_data, all_test_data);
+ }
+ SUBCASE("Can get correct ranges for local variables") {
+ Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "test");
+ test_resolve_symbols(uri, test_data, all_test_data);
+ }
+ SUBCASE("Can get correct ranges for local parameters") {
+ Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "arg");
+ test_resolve_symbols(uri, test_data, all_test_data);
+ }
+ }
+
+ SUBCASE("Can get correct ranges for indented variables") {
+ String path = "res://lsp/indentation.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for scopes") {
+ String path = "res://lsp/scopes.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for lambda") {
+ String path = "res://lsp/lambdas.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for inner class") {
+ String path = "res://lsp/class.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for inner class") {
+ String path = "res://lsp/enums.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for shadowing & shadowed variables") {
+ String path = "res://lsp/shadowing_initializer.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ SUBCASE("Can get correct ranges for properties and getter/setter") {
+ String path = "res://lsp/properties.notest.gd";
+ assert_no_errors_in(path);
+ String uri = workspace->get_file_uri(path);
+ Vector<InlineTestData> all_test_data = read_tests(path);
+ test_resolve_symbols(uri, all_test_data, all_test_data);
+ }
+
+ memdelete(proto);
+ finish_language();
+ }
+}
+
+} // namespace GDScriptTests
+
+#endif // TOOLS_ENABLED
+
+#endif // TEST_LSP_H
diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub
index 22ef1b5ea9..a34a97d230 100644
--- a/modules/glslang/SCsub
+++ b/modules/glslang/SCsub
@@ -12,6 +12,8 @@ thirdparty_obj = []
if env["builtin_glslang"]:
thirdparty_dir = "#thirdparty/glslang/"
thirdparty_sources = [
+ "glslang/GenericCodeGen/CodeGen.cpp",
+ "glslang/GenericCodeGen/Link.cpp",
"glslang/MachineIndependent/attribute.cpp",
"glslang/MachineIndependent/Constant.cpp",
"glslang/MachineIndependent/glslang_tab.cpp",
@@ -40,8 +42,7 @@ if env["builtin_glslang"]:
"glslang/MachineIndependent/SpirvIntrinsics.cpp",
"glslang/MachineIndependent/SymbolTable.cpp",
"glslang/MachineIndependent/Versions.cpp",
- "glslang/GenericCodeGen/CodeGen.cpp",
- "glslang/GenericCodeGen/Link.cpp",
+ "glslang/ResourceLimits/ResourceLimits.cpp",
"OGLCompilersDLL/InitializeDll.cpp",
"SPIRV/disassemble.cpp",
"SPIRV/doc.cpp",
diff --git a/modules/glslang/glslang_resource_limits.h b/modules/glslang/glslang_resource_limits.h
deleted file mode 100644
index 8340e63096..0000000000
--- a/modules/glslang/glslang_resource_limits.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/**************************************************************************/
-/* glslang_resource_limits.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 GLSLANG_RESOURCE_LIMITS_H
-#define GLSLANG_RESOURCE_LIMITS_H
-
-#include <glslang/Include/ResourceLimits.h>
-
-// Synchronized with upstream glslang/StandAlone/ResourceLimits.cpp which is not
-// part of the public API.
-
-const TBuiltInResource DefaultTBuiltInResource = {
- /* .MaxLights = */ 32,
- /* .MaxClipPlanes = */ 6,
- /* .MaxTextureUnits = */ 32,
- /* .MaxTextureCoords = */ 32,
- /* .MaxVertexAttribs = */ 64,
- /* .MaxVertexUniformComponents = */ 4096,
- /* .MaxVaryingFloats = */ 64,
- /* .MaxVertexTextureImageUnits = */ 32,
- /* .MaxCombinedTextureImageUnits = */ 80,
- /* .MaxTextureImageUnits = */ 32,
- /* .MaxFragmentUniformComponents = */ 4096,
- /* .MaxDrawBuffers = */ 32,
- /* .MaxVertexUniformVectors = */ 128,
- /* .MaxVaryingVectors = */ 8,
- /* .MaxFragmentUniformVectors = */ 16,
- /* .MaxVertexOutputVectors = */ 16,
- /* .MaxFragmentInputVectors = */ 15,
- /* .MinProgramTexelOffset = */ -8,
- /* .MaxProgramTexelOffset = */ 7,
- /* .MaxClipDistances = */ 8,
- /* .MaxComputeWorkGroupCountX = */ 65535,
- /* .MaxComputeWorkGroupCountY = */ 65535,
- /* .MaxComputeWorkGroupCountZ = */ 65535,
- /* .MaxComputeWorkGroupSizeX = */ 1024,
- /* .MaxComputeWorkGroupSizeY = */ 1024,
- /* .MaxComputeWorkGroupSizeZ = */ 64,
- /* .MaxComputeUniformComponents = */ 1024,
- /* .MaxComputeTextureImageUnits = */ 16,
- /* .MaxComputeImageUniforms = */ 8,
- /* .MaxComputeAtomicCounters = */ 8,
- /* .MaxComputeAtomicCounterBuffers = */ 1,
- /* .MaxVaryingComponents = */ 60,
- /* .MaxVertexOutputComponents = */ 64,
- /* .MaxGeometryInputComponents = */ 64,
- /* .MaxGeometryOutputComponents = */ 128,
- /* .MaxFragmentInputComponents = */ 128,
- /* .MaxImageUnits = */ 8,
- /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
- /* .MaxCombinedShaderOutputResources = */ 8,
- /* .MaxImageSamples = */ 0,
- /* .MaxVertexImageUniforms = */ 0,
- /* .MaxTessControlImageUniforms = */ 0,
- /* .MaxTessEvaluationImageUniforms = */ 0,
- /* .MaxGeometryImageUniforms = */ 0,
- /* .MaxFragmentImageUniforms = */ 8,
- /* .MaxCombinedImageUniforms = */ 8,
- /* .MaxGeometryTextureImageUnits = */ 16,
- /* .MaxGeometryOutputVertices = */ 256,
- /* .MaxGeometryTotalOutputComponents = */ 1024,
- /* .MaxGeometryUniformComponents = */ 1024,
- /* .MaxGeometryVaryingComponents = */ 64,
- /* .MaxTessControlInputComponents = */ 128,
- /* .MaxTessControlOutputComponents = */ 128,
- /* .MaxTessControlTextureImageUnits = */ 16,
- /* .MaxTessControlUniformComponents = */ 1024,
- /* .MaxTessControlTotalOutputComponents = */ 4096,
- /* .MaxTessEvaluationInputComponents = */ 128,
- /* .MaxTessEvaluationOutputComponents = */ 128,
- /* .MaxTessEvaluationTextureImageUnits = */ 16,
- /* .MaxTessEvaluationUniformComponents = */ 1024,
- /* .MaxTessPatchComponents = */ 120,
- /* .MaxPatchVertices = */ 32,
- /* .MaxTessGenLevel = */ 64,
- /* .MaxViewports = */ 16,
- /* .MaxVertexAtomicCounters = */ 0,
- /* .MaxTessControlAtomicCounters = */ 0,
- /* .MaxTessEvaluationAtomicCounters = */ 0,
- /* .MaxGeometryAtomicCounters = */ 0,
- /* .MaxFragmentAtomicCounters = */ 8,
- /* .MaxCombinedAtomicCounters = */ 8,
- /* .MaxAtomicCounterBindings = */ 1,
- /* .MaxVertexAtomicCounterBuffers = */ 0,
- /* .MaxTessControlAtomicCounterBuffers = */ 0,
- /* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
- /* .MaxGeometryAtomicCounterBuffers = */ 0,
- /* .MaxFragmentAtomicCounterBuffers = */ 1,
- /* .MaxCombinedAtomicCounterBuffers = */ 1,
- /* .MaxAtomicCounterBufferSize = */ 16384,
- /* .MaxTransformFeedbackBuffers = */ 4,
- /* .MaxTransformFeedbackInterleavedComponents = */ 64,
- /* .MaxCullDistances = */ 8,
- /* .MaxCombinedClipAndCullDistances = */ 8,
- /* .MaxSamples = */ 4,
- /* .maxMeshOutputVerticesNV = */ 256,
- /* .maxMeshOutputPrimitivesNV = */ 512,
- /* .maxMeshWorkGroupSizeX_NV = */ 32,
- /* .maxMeshWorkGroupSizeY_NV = */ 1,
- /* .maxMeshWorkGroupSizeZ_NV = */ 1,
- /* .maxTaskWorkGroupSizeX_NV = */ 32,
- /* .maxTaskWorkGroupSizeY_NV = */ 1,
- /* .maxTaskWorkGroupSizeZ_NV = */ 1,
- /* .maxMeshViewCountNV = */ 4,
- /* .maxMeshOutputVerticesEXT = */ 256,
- /* .maxMeshOutputPrimitivesEXT = */ 256,
- /* .maxMeshWorkGroupSizeX_EXT = */ 128,
- /* .maxMeshWorkGroupSizeY_EXT = */ 128,
- /* .maxMeshWorkGroupSizeZ_EXT = */ 128,
- /* .maxTaskWorkGroupSizeX_EXT = */ 128,
- /* .maxTaskWorkGroupSizeY_EXT = */ 128,
- /* .maxTaskWorkGroupSizeZ_EXT = */ 128,
- /* .maxMeshViewCountEXT = */ 4,
- /* .maxDualSourceDrawBuffersEXT = */ 1,
-
- /* .limits = */ {
- /* .nonInductiveForLoops = */ 1,
- /* .whileLoops = */ 1,
- /* .doWhileLoops = */ 1,
- /* .generalUniformIndexing = */ 1,
- /* .generalAttributeMatrixVectorIndexing = */ 1,
- /* .generalVaryingIndexing = */ 1,
- /* .generalSamplerIndexing = */ 1,
- /* .generalVariableIndexing = */ 1,
- /* .generalConstantMatrixVectorIndexing = */ 1,
- }
-};
-
-#endif // GLSLANG_RESOURCE_LIMITS_H
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index 622910761d..7fe3a57880 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -30,11 +30,11 @@
#include "register_types.h"
-#include "glslang_resource_limits.h"
-
+#include "core/config/engine.h"
#include "servers/rendering/rendering_device.h"
#include <glslang/Include/Types.h>
+#include <glslang/Public/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/SPIRV/GlslangToSpv.h>
@@ -56,7 +56,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
- glslang::TShader::ForbidIncluder includer;
if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
@@ -127,26 +126,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
}
EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
- const int DefaultVersion = 100;
- std::string pre_processed_code;
-
- //preprocess
- if (!shader.preprocess(&DefaultTBuiltInResource, DefaultVersion, ENoProfile, false, false, messages, &pre_processed_code, includer)) {
- if (r_error) {
- (*r_error) = "Failed pre-process:\n";
- (*r_error) += shader.getInfoLog();
- (*r_error) += "\n";
- (*r_error) += shader.getInfoDebugLog();
- }
-
- return ret;
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ messages = (EShMessages)(messages | EShMsgDebugInfo);
}
- //set back..
- cs_strings = pre_processed_code.c_str();
- shader.setStrings(&cs_strings, 1);
+ const int DefaultVersion = 100;
//parse
- if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) {
+ if (!shader.parse(GetDefaultResources(), DefaultVersion, false, messages)) {
if (r_error) {
(*r_error) = "Failed parse:\n";
(*r_error) += shader.getInfoLog();
@@ -174,6 +160,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
std::vector<uint32_t> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
+
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ spvOptions.generateDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugSource = true;
+ }
+
glslang::GlslangToSpv(*program.getIntermediate(stages[p_stage]), SpirV, &logger, &spvOptions);
ret.resize(SpirV.size() * sizeof(uint32_t));
@@ -188,7 +181,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
String version;
- version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
return version;
}
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 5bc6081803..5c10b76e0a 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -1,11 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFDocument" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Class for importing and exporting glTF files in and out of Godot.
</brief_description>
<description>
- Append a glTF2 3d format from a file, buffer or scene and then write to the filesystem, buffer or scene.
+ GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene.
+ All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects.
+ 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="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>
<methods>
<method name="append_from_buffer">
@@ -82,4 +87,28 @@
</description>
</method>
</methods>
+ <members>
+ <member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default="&quot;PNG&quot;">
+ The user-friendly name of the export image format. This is used when exporting the GLTF file, including writing to a file and writing to a byte array.
+ By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes.
+ </member>
+ <member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75">
+ If [member image_format] is a lossy image format, this determines the lossy quality of the image. On a range of [code]0.0[/code] to [code]1.0[/code], where [code]0.0[/code] is the lowest quality and [code]1.0[/code] is the highest quality. A lossy quality of [code]1.0[/code] is not the same as lossless.
+ </member>
+ <member name="root_node_mode" type="int" setter="set_root_node_mode" getter="get_root_node_mode" enum="GLTFDocument.RootNodeMode" default="0">
+ How to process the root node during export. See [enum RootNodeMode] for details. The default and recommended value is [constant ROOT_NODE_MODE_SINGLE_ROOT].
+ [b]Note:[/b] Regardless of how the glTF file is exported, when importing, the root node type and name can be overridden in the scene import settings tab.
+ </member>
+ </members>
+ <constants>
+ <constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode">
+ Treat the Godot scene's root node as the root node of the glTF file, and mark it as the single root node via the [code]GODOT_single_root[/code] glTF extension. This will be parsed the same as [constant ROOT_NODE_MODE_KEEP_ROOT] if the implementation does not support [code]GODOT_single_root[/code].
+ </constant>
+ <constant name="ROOT_NODE_MODE_KEEP_ROOT" value="1" enum="RootNodeMode">
+ Treat the Godot scene's root node as the root node of the glTF file, but do not mark it as anything special. An extra root node will be generated when importing into Godot. This uses only vanilla glTF features. This is equivalent to the behavior in Godot 4.1 and earlier.
+ </constant>
+ <constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode">
+ Treat the Godot scene's root node as the name of the glTF scene, and add all of its children as root nodes of the glTF file. This uses only vanilla glTF features. This avoids an extra root node, but only the name of the Godot scene's root node will be preserved, as it will not be saved as a node.
+ </constant>
+ </constants>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index 927ffb6aae..eee62845ca 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -17,7 +17,7 @@
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="scene_node" type="Node" />
<description>
- Part of the export process. This method is run after [method _export_preflight] and before [method _export_node].
+ Part of the export process. This method is run after [method _export_preflight] and before [method _export_preserialize].
Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node].
</description>
</method>
@@ -28,7 +28,7 @@
<param index="2" name="json" type="Dictionary" />
<param index="3" name="node" type="Node" />
<description>
- Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post].
+ 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.
</description>
</method>
@@ -49,14 +49,36 @@
The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
</description>
</method>
+ <method name="_export_preserialize" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="state" type="GLTFState" />
+ <description>
+ Part of the export process. This method is run after [method _convert_scene_node] and before [method _get_saveable_image_formats].
+ This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem].
+ </description>
+ </method>
<method name="_generate_scene_node" qualifiers="virtual">
<return type="Node3D" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="scene_parent" type="Node" />
<description>
- Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_post_parse].
+ Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node].
Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
+ [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node.
+ </description>
+ </method>
+ <method name="_get_image_file_extension" qualifiers="virtual">
+ <return type="String" />
+ <description>
+ Returns the file extension to use for saving image data into, for example, [code]".png"[/code]. If defined, when this extension is used to handle images, and the images are saved to a separate file, the image bytes will be copied to a file with this extension. If this is set, there should be a [ResourceImporter] class able to import the file. If not defined or empty, Godot will save the image into a PNG file.
+ </description>
+ </method>
+ <method name="_get_saveable_image_formats" qualifiers="virtual">
+ <return type="PackedStringArray" />
+ <description>
+ Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_node].
+ Returns an array of the image formats that can be saved/exported by this extension. This extension will only be selected as the image exporter if the [GLTFDocument]'s [member GLTFDocument.image_format] is in this array. If this [GLTFDocumentExtension] is selected as the image exporter, one of the [method _save_image_at_path] or [method _serialize_image_to_bytes] methods will run next, otherwise [method _export_node] will run next. If the format name contains [code]"Lossy"[/code], the lossy quality slider will be displayed.
</description>
</method>
<method name="_get_supported_extensions" qualifiers="virtual">
@@ -73,7 +95,7 @@
<param index="2" name="json" type="Dictionary" />
<param index="3" name="node" type="Node" />
<description>
- Part of the import process. This method is run after [method _import_post_parse] and before [method _import_post].
+ Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_post].
This method can be used to make modifications to each of the generated Godot scene nodes.
</description>
</method>
@@ -90,7 +112,7 @@
<return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<description>
- Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node].
+ 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.
</description>
</method>
@@ -120,7 +142,7 @@
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="extensions" type="Dictionary" />
<description>
- Part of the import process. This method is run after [method _get_supported_extensions] and before [method _generate_scene_node].
+ Part of the import process. This method is run after [method _get_supported_extensions] and before [method _import_post_parse].
Runs when parsing the node extensions of a GLTFNode. This method can be used to process the extension JSON data into a format that can be used by [method _generate_scene_node]. The return value should be a member of the [enum Error] enum.
</description>
</method>
@@ -134,5 +156,41 @@
Runs when parsing the texture JSON from the GLTF textures array. This can be used to set the source image index to use as the texture.
</description>
</method>
+ <method name="_save_image_at_path" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="image" type="Image" />
+ <param index="2" name="file_path" type="String" />
+ <param index="3" name="image_format" type="String" />
+ <param index="4" name="lossy_quality" type="float" />
+ <description>
+ Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json].
+ This method is run when saving images separately from the GLTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
+ </description>
+ </method>
+ <method name="_serialize_image_to_bytes" qualifiers="virtual">
+ <return type="PackedByteArray" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="image" type="Image" />
+ <param index="2" name="image_dict" type="Dictionary" />
+ <param index="3" name="image_format" type="String" />
+ <param index="4" name="lossy_quality" type="float" />
+ <description>
+ Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json].
+ This method is run when embedding images in the GLTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
+ This method must set the image MIME type in the [param image_dict] with the [code]"mimeType"[/code] key. For example, for a PNG image, it would be set to [code]"image/png"[/code]. The return value must be a [PackedByteArray] containing the image data.
+ </description>
+ </method>
+ <method name="_serialize_texture_json" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="texture_json" type="Dictionary" />
+ <param index="2" name="gltf_texture" type="GLTFTexture" />
+ <param index="3" name="image_format" type="String" />
+ <description>
+ Part of the export process. This method is run after [method _save_image_at_path] or [method _serialize_image_to_bytes], and before [method _export_node]. Note that this method only runs when this [GLTFDocumentExtension] is selected as the image exporter.
+ This method can be used to set up the extensions for the texture JSON by editing [param texture_json]. The extension must also be added as used extension with [method GLTFState.add_used_extension], be sure to set [code]required[/code] to [code]true[/code] if you are not providing a fallback.
+ </description>
+ </method>
</methods>
</class>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index 3aab1c5183..d364069193 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -42,7 +42,10 @@
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. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
+ 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>
+ <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)">
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".
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index 32023de696..ba1c531283 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="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link>
</tutorials>
<methods>
<method name="add_used_extension">
@@ -266,11 +267,18 @@
</methods>
<members>
<member name="base_path" type="String" setter="set_base_path" getter="get_base_path" default="&quot;&quot;">
+ The folder path associated with this GLTF data. This is used to find other files the GLTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file.
</member>
<member name="buffers" type="PackedByteArray[]" setter="set_buffers" getter="get_buffers" default="[]">
</member>
+ <member name="copyright" type="String" setter="set_copyright" getter="get_copyright" default="&quot;&quot;">
+ The copyright string in the asset header of the GLTF file. This is set during import if present and export if non-empty. See the GLTF asset header documentation for more information.
+ </member>
<member name="create_animations" type="bool" setter="set_create_animations" getter="get_create_animations" default="true">
</member>
+ <member name="filename" type="String" setter="set_filename" getter="get_filename" default="&quot;&quot;">
+ The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string.
+ </member>
<member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()">
</member>
<member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}">
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 4967913b85..cb45a6589e 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -40,6 +40,7 @@
#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 "main/main.h"
#include "scene/gui/line_edit.h"
@@ -381,10 +382,10 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path
path_status->set_text(error);
if (success) {
- path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("success_color"), SNAME("Editor")));
+ path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
configure_blender_dialog->get_ok_button()->set_disabled(false);
} else {
- path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
configure_blender_dialog->get_ok_button()->set_disabled(true);
}
}
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 2804a8b0a2..582bcf466b 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -36,6 +36,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_supported_extensions);
GDVIRTUAL_BIND(_parse_node_extensions, "state", "gltf_node", "extensions");
GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image");
+ GDVIRTUAL_BIND(_get_image_file_extension);
GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture");
GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent");
GDVIRTUAL_BIND(_import_post_parse, "state");
@@ -44,6 +45,11 @@ void GLTFDocumentExtension::_bind_methods() {
// Export process.
GDVIRTUAL_BIND(_export_preflight, "state", "root");
GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node");
+ GDVIRTUAL_BIND(_export_preserialize, "state");
+ GDVIRTUAL_BIND(_get_saveable_image_formats);
+ GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality");
+ GDVIRTUAL_BIND(_save_image_at_path, "state", "image", "file_path", "image_format", "lossy_quality");
+ GDVIRTUAL_BIND(_serialize_texture_json, "state", "texture_json", "gltf_texture", "image_format");
GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node");
GDVIRTUAL_BIND(_export_post, "state");
}
@@ -78,6 +84,12 @@ Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const Pack
return err;
}
+String GLTFDocumentExtension::get_image_file_extension() {
+ String ret;
+ GDVIRTUAL_CALL(_get_image_file_extension, ret);
+ return ret;
+}
+
Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER);
@@ -89,7 +101,6 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di
Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
ERR_FAIL_NULL_V(p_state, nullptr);
ERR_FAIL_NULL_V(p_gltf_node, nullptr);
- ERR_FAIL_NULL_V(p_scene_parent, nullptr);
Node3D *ret_node = nullptr;
GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node);
return ret_node;
@@ -134,6 +145,43 @@ void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFN
GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node);
}
+Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) {
+ ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ Error err = OK;
+ GDVIRTUAL_CALL(_export_preserialize, p_state, err);
+ return err;
+}
+
+Vector<String> GLTFDocumentExtension::get_saveable_image_formats() {
+ Vector<String> ret;
+ GDVIRTUAL_CALL(_get_saveable_image_formats, ret);
+ return ret;
+}
+
+PackedByteArray GLTFDocumentExtension::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) {
+ PackedByteArray ret;
+ ERR_FAIL_NULL_V(p_state, ret);
+ ERR_FAIL_NULL_V(p_image, ret);
+ GDVIRTUAL_CALL(_serialize_image_to_bytes, p_state, p_image, p_image_dict, p_image_format, p_lossy_quality, ret);
+ return ret;
+}
+
+Error GLTFDocumentExtension::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) {
+ ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER);
+ Error ret = OK;
+ GDVIRTUAL_CALL(_save_image_at_path, p_state, p_image, p_file_path, p_image_format, p_lossy_quality, ret);
+ return ret;
+}
+
+Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) {
+ ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_gltf_texture, ERR_INVALID_PARAMETER);
+ Error err = OK;
+ GDVIRTUAL_CALL(_serialize_texture_json, p_state, p_texture_json, p_gltf_texture, p_image_format, err);
+ return err;
+}
+
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);
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index d922588a29..512b7aba91 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -45,14 +45,20 @@ public:
virtual Vector<String> get_supported_extensions();
virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions);
virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image);
+ virtual String get_image_file_extension();
virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture);
- virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
virtual Error import_post_parse(Ref<GLTFState> p_state);
+ virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error import_post(Ref<GLTFState> p_state, Node *p_node);
// Export process.
virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root);
virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
+ virtual Error export_preserialize(Ref<GLTFState> p_state);
+ virtual Vector<String> get_saveable_image_formats();
+ virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality);
+ virtual Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality);
+ virtual Error serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format);
virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error export_post(Ref<GLTFState> p_state);
@@ -61,6 +67,7 @@ public:
GDVIRTUAL0R(Vector<String>, _get_supported_extensions);
GDVIRTUAL3R(Error, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary);
GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>);
+ GDVIRTUAL0R(String, _get_image_file_extension);
GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>);
GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>);
@@ -69,6 +76,11 @@ public:
// Export process.
GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *);
GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
+ GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>);
+ GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats);
+ GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float);
+ GDVIRTUAL5R(Error, _save_image_at_path, Ref<GLTFState>, Ref<Image>, String, String, float);
+ GDVIRTUAL4R(Error, _serialize_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>, String);
GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>);
};
diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp
index 72764036e1..ca61a24201 100644
--- a/modules/denoise/lightmap_denoiser.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* lightmap_denoiser.cpp */
+/* gltf_document_extension_texture_ktx.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,38 +28,39 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "lightmap_denoiser.h"
+#include "gltf_document_extension_texture_ktx.h"
-#include "denoise_wrapper.h"
-
-#include "core/io/image.h"
-
-LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() {
- return memnew(LightmapDenoiserOIDN);
+// Import process.
+Error GLTFDocumentExtensionTextureKTX::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
+ if (!p_extensions.has("KHR_texture_basisu")) {
+ return ERR_SKIP;
+ }
+ return OK;
}
-void LightmapDenoiserOIDN::make_default_denoiser() {
- create_function = create_oidn_denoiser;
+Vector<String> GLTFDocumentExtensionTextureKTX::get_supported_extensions() {
+ Vector<String> ret;
+ ret.push_back("KHR_texture_basisu");
+ return ret;
}
-Ref<Image> LightmapDenoiserOIDN::denoise_image(const Ref<Image> &p_image) {
- Ref<Image> img = p_image->duplicate();
-
- img->convert(Image::FORMAT_RGBF);
-
- Vector<uint8_t> data = img->get_data();
- if (!oidn_denoise(device, (float *)data.ptrw(), img->get_width(), img->get_height())) {
- return p_image;
+Error GLTFDocumentExtensionTextureKTX::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) {
+ if (p_mime_type == "image/ktx2") {
+ return r_image->load_ktx_from_buffer(p_image_data);
}
-
- img->set_data(img->get_width(), img->get_height(), false, img->get_format(), data);
- return img;
-}
-
-LightmapDenoiserOIDN::LightmapDenoiserOIDN() {
- device = oidn_denoiser_init();
+ return OK;
}
-LightmapDenoiserOIDN::~LightmapDenoiserOIDN() {
- oidn_denoiser_finish(device);
+Error GLTFDocumentExtensionTextureKTX::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
+ if (!p_texture_json.has("extensions")) {
+ return OK;
+ }
+ const Dictionary &extensions = p_texture_json["extensions"];
+ if (!extensions.has("KHR_texture_basisu")) {
+ return OK;
+ }
+ const Dictionary &texture_ktx = extensions["KHR_texture_basisu"];
+ ERR_FAIL_COND_V(!texture_ktx.has("source"), ERR_PARSE_ERROR);
+ r_gltf_texture->set_src_image(texture_ktx["source"]);
+ return OK;
}
diff --git a/modules/denoise/lightmap_denoiser.h b/modules/gltf/extensions/gltf_document_extension_texture_ktx.h
index 8f658ab096..e4cb38a044 100644
--- a/modules/denoise/lightmap_denoiser.h
+++ b/modules/gltf/extensions/gltf_document_extension_texture_ktx.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* lightmap_denoiser.h */
+/* gltf_document_extension_texture_ktx.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef LIGHTMAP_DENOISER_H
-#define LIGHTMAP_DENOISER_H
+#ifndef GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
+#define GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
-#include "core/object/class_db.h"
-#include "scene/3d/lightmapper.h"
+#include "gltf_document_extension.h"
-struct OIDNDeviceImpl;
-
-class LightmapDenoiserOIDN : public LightmapDenoiser {
- GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser);
-
-protected:
- void *device = nullptr;
+class GLTFDocumentExtensionTextureKTX : public GLTFDocumentExtension {
+ GDCLASS(GLTFDocumentExtensionTextureKTX, GLTFDocumentExtension);
public:
- static LightmapDenoiser *create_oidn_denoiser();
-
- Ref<Image> denoise_image(const Ref<Image> &p_image) override;
-
- static void make_default_denoiser();
-
- LightmapDenoiserOIDN();
- ~LightmapDenoiserOIDN();
+ // Import process.
+ Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
+ Vector<String> get_supported_extensions() override;
+ Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override;
+ Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override;
};
-#endif // LIGHTMAP_DENOISER_H
+#endif // GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
index ded4970968..f8bd6d57cf 100644
--- a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
@@ -30,8 +30,6 @@
#include "gltf_document_extension_texture_webp.h"
-#include "scene/3d/area_3d.h"
-
// Import process.
Error GLTFDocumentExtensionTextureWebP::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
if (!p_extensions.has("EXT_texture_webp")) {
@@ -53,6 +51,10 @@ Error GLTFDocumentExtensionTextureWebP::parse_image_data(Ref<GLTFState> p_state,
return OK;
}
+String GLTFDocumentExtensionTextureWebP::get_image_file_extension() {
+ return ".webp";
+}
+
Error GLTFDocumentExtensionTextureWebP::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
if (!p_texture_json.has("extensions")) {
return OK;
@@ -66,3 +68,42 @@ Error GLTFDocumentExtensionTextureWebP::parse_texture_json(Ref<GLTFState> p_stat
r_gltf_texture->set_src_image(texture_webp["source"]);
return OK;
}
+
+Vector<String> GLTFDocumentExtensionTextureWebP::get_saveable_image_formats() {
+ Vector<String> ret;
+ ret.push_back("Lossless WebP");
+ ret.push_back("Lossy WebP");
+ return ret;
+}
+
+PackedByteArray GLTFDocumentExtensionTextureWebP::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) {
+ if (p_image_format == "Lossless WebP") {
+ p_image_dict["mimeType"] = "image/webp";
+ return p_image->save_webp_to_buffer(false);
+ } else if (p_image_format == "Lossy WebP") {
+ p_image_dict["mimeType"] = "image/webp";
+ return p_image->save_webp_to_buffer(true, p_lossy_quality);
+ }
+ ERR_FAIL_V(PackedByteArray());
+}
+
+Error GLTFDocumentExtensionTextureWebP::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) {
+ if (p_image_format == "Lossless WebP") {
+ p_image->save_webp(p_file_path, false);
+ return OK;
+ } else if (p_image_format == "Lossy WebP") {
+ p_image->save_webp(p_file_path, true, p_lossy_quality);
+ return OK;
+ }
+ return ERR_INVALID_PARAMETER;
+}
+
+Error GLTFDocumentExtensionTextureWebP::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) {
+ Dictionary ext_texture_webp;
+ ext_texture_webp["source"] = p_gltf_texture->get_src_image();
+ Dictionary texture_extensions;
+ texture_extensions["EXT_texture_webp"] = ext_texture_webp;
+ p_texture_json["extensions"] = texture_extensions;
+ p_state->add_used_extension("EXT_texture_webp", true);
+ return OK;
+}
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.h b/modules/gltf/extensions/gltf_document_extension_texture_webp.h
index 9abf09a41f..2113bd4768 100644
--- a/modules/gltf/extensions/gltf_document_extension_texture_webp.h
+++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.h
@@ -41,7 +41,13 @@ public:
Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
Vector<String> get_supported_extensions() override;
Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override;
+ String get_image_file_extension() override;
Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override;
+ // Export process.
+ Vector<String> get_saveable_image_formats() override;
+ PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) override;
+ Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_full_path, const String &p_image_format, float p_lossy_quality) override;
+ Error serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) override;
};
#endif // GLTF_DOCUMENT_EXTENSION_TEXTURE_WEBP_H
diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp
index c60b522168..435a1260ba 100644
--- a/modules/gltf/extensions/gltf_light.cpp
+++ b/modules/gltf/extensions/gltf_light.cpp
@@ -111,7 +111,7 @@ void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) {
Ref<GLTFLight> GLTFLight::from_node(const Light3D *p_light) {
Ref<GLTFLight> l;
l.instantiate();
- ERR_FAIL_COND_V_MSG(!p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null.");
+ ERR_FAIL_NULL_V_MSG(p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null.");
l->color = p_light->get_color();
if (cast_to<DirectionalLight3D>(p_light)) {
l->light_type = "directional";
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index 3b0fad064a..b80f4348c2 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -48,6 +48,8 @@ void GLTFPhysicsBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &GLTFPhysicsBody::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &GLTFPhysicsBody::get_angular_velocity);
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_tensor"), &GLTFPhysicsBody::get_inertia_tensor);
ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_inertia_tensor);
@@ -55,6 +57,7 @@ void GLTFPhysicsBody::_bind_methods() {
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::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor");
}
@@ -90,6 +93,14 @@ void GLTFPhysicsBody::set_angular_velocity(Vector3 p_angular_velocity) {
angular_velocity = p_angular_velocity;
}
+Vector3 GLTFPhysicsBody::get_center_of_mass() const {
+ return center_of_mass;
+}
+
+void GLTFPhysicsBody::set_center_of_mass(const Vector3 &p_center_of_mass) {
+ center_of_mass = p_center_of_mass;
+}
+
Basis GLTFPhysicsBody::get_inertia_tensor() const {
return inertia_tensor;
}
@@ -101,7 +112,7 @@ void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) {
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
- ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null.");
+ 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";
} else if (cast_to<AnimatableBody3D>(p_body_node)) {
@@ -111,6 +122,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
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);
if (body->get_center_of_mass() != Vector3()) {
@@ -145,6 +157,7 @@ CollisionObject3D *GLTFPhysicsBody::to_node() const {
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") {
@@ -154,6 +167,7 @@ CollisionObject3D *GLTFPhysicsBody::to_node() const {
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") {
@@ -193,6 +207,14 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
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 (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) {
@@ -230,6 +252,14 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[2] = angular_velocity.z;
d["angularVelocity"] = velocity_array;
}
+ if (center_of_mass != Vector3()) {
+ Array center_of_mass_array;
+ center_of_mass_array.resize(3);
+ 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;
+ }
if (inertia_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) {
Array inertia_array;
inertia_array.resize(9);
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h
index 5fedb4f111..391b4b873f 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.h
+++ b/modules/gltf/extensions/physics/gltf_physics_body.h
@@ -45,8 +45,9 @@ protected:
private:
String body_type = "static";
real_t mass = 1.0;
- Vector3 linear_velocity = Vector3();
- Vector3 angular_velocity = Vector3();
+ Vector3 linear_velocity;
+ Vector3 angular_velocity;
+ Vector3 center_of_mass;
Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0);
public:
@@ -62,6 +63,9 @@ public:
Vector3 get_angular_velocity() const;
void set_angular_velocity(Vector3 p_angular_velocity);
+ Vector3 get_center_of_mass() const;
+ void set_center_of_mass(const Vector3 &p_center_of_mass);
+
Basis get_inertia_tensor() const;
void set_inertia_tensor(Basis p_inertia_tensor);
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index d828363e03..bac988630d 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -42,7 +42,6 @@
#include "core/io/stream_peer.h"
#include "core/math/disjoint_set.h"
#include "core/version.h"
-#include "drivers/png/png_driver_common.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
@@ -110,11 +109,17 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
return importer_mesh;
}
-Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
+Error GLTFDocument::_serialize(Ref<GLTFState> p_state) {
if (!p_state->buffers.size()) {
p_state->buffers.push_back(Vector<uint8_t>());
}
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Error err = ext->export_preserialize(p_state);
+ ERR_CONTINUE(err != OK);
+ }
+
/* STEP CONVERT MESH INSTANCES */
_convert_mesh_instances(p_state);
@@ -161,7 +166,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE IMAGES */
- err = _serialize_images(p_state, p_path);
+ err = _serialize_images(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -207,7 +212,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE VERSION */
- err = _serialize_version(p_state);
+ err = _serialize_asset_header(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -243,23 +248,18 @@ Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
}
Error GLTFDocument::_serialize_scenes(Ref<GLTFState> p_state) {
+ ERR_FAIL_COND_V_MSG(p_state->root_nodes.size() == 0, ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
+ // Godot only supports one scene per glTF file.
Array scenes;
- const int loaded_scene = 0;
- p_state->json["scene"] = loaded_scene;
-
- if (p_state->nodes.size()) {
- Dictionary s;
- if (!p_state->scene_name.is_empty()) {
- s["name"] = p_state->scene_name;
- }
-
- Array nodes;
- nodes.push_back(0);
- s["nodes"] = nodes;
- scenes.push_back(s);
- }
+ Dictionary scene_dict;
+ scenes.append(scene_dict);
p_state->json["scenes"] = scenes;
-
+ p_state->json["scene"] = 0;
+ // Add nodes to the scene dict.
+ scene_dict["nodes"] = p_state->root_nodes;
+ if (!p_state->scene_name.is_empty()) {
+ scene_dict["name"] = p_state->scene_name;
+ }
return OK;
}
@@ -562,17 +562,17 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) {
if (scenes.size()) {
ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT);
- const Dictionary &s = scenes[loaded_scene];
- ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE);
- const Array &nodes = s["nodes"];
+ const Dictionary &scene_dict = scenes[loaded_scene];
+ ERR_FAIL_COND_V(!scene_dict.has("nodes"), ERR_UNAVAILABLE);
+ const Array &nodes = scene_dict["nodes"];
for (int j = 0; j < nodes.size(); j++) {
p_state->root_nodes.push_back(nodes[j]);
}
-
- if (s.has("name") && !String(s["name"]).is_empty() && !((String)s["name"]).begins_with("Scene")) {
- p_state->scene_name = _gen_unique_name(p_state, s["name"]);
+ // 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 {
- p_state->scene_name = _gen_unique_name(p_state, p_state->filename);
+ p_state->scene_name = p_state->filename;
}
}
@@ -3000,17 +3000,77 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
return OK;
}
-Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_path) {
+void GLTFDocument::set_image_format(const String &p_image_format) {
+ _image_format = p_image_format;
+}
+
+String GLTFDocument::get_image_format() const {
+ return _image_format;
+}
+
+void GLTFDocument::set_lossy_quality(float p_lossy_quality) {
+ _lossy_quality = p_lossy_quality;
+}
+
+float GLTFDocument::get_lossy_quality() const {
+ return _lossy_quality;
+}
+
+Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
Array images;
+ // Check if any extension wants to be the image saver.
+ _image_save_extension = Ref<GLTFDocumentExtension>();
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Vector<String> image_formats = ext->get_saveable_image_formats();
+ if (image_formats.has(_image_format)) {
+ _image_save_extension = ext;
+ break;
+ }
+ }
+ // Serialize every image in the state's images array.
for (int i = 0; i < p_state->images.size(); i++) {
- Dictionary d;
+ Dictionary image_dict;
ERR_CONTINUE(p_state->images[i].is_null());
Ref<Image> image = p_state->images[i]->get_image();
ERR_CONTINUE(image.is_null());
+ if (image->is_compressed()) {
+ image->decompress();
+ ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed.");
+ }
- if (p_path.to_lower().ends_with("glb") || p_path.is_empty()) {
+ if (p_state->filename.to_lower().ends_with("gltf")) {
+ String img_name = p_state->images[i]->get_name();
+ if (img_name.is_empty()) {
+ img_name = itos(i);
+ }
+ img_name = _gen_unique_name(p_state, img_name);
+ img_name = img_name.pad_zeros(3);
+ String relative_texture_dir = "textures";
+ String full_texture_dir = p_state->base_path.path_join(relative_texture_dir);
+ Ref<DirAccess> da = DirAccess::open(p_state->base_path);
+ ERR_FAIL_COND_V(da.is_null(), FAILED);
+
+ if (!da->dir_exists(full_texture_dir)) {
+ da->make_dir(full_texture_dir);
+ }
+ if (_image_save_extension.is_valid()) {
+ img_name = img_name + _image_save_extension->get_image_file_extension();
+ Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file.");
+ } else if (_image_format == "PNG") {
+ img_name = img_name + ".png";
+ image->save_png(full_texture_dir.path_join(img_name));
+ } else if (_image_format == "JPEG") {
+ img_name = img_name + ".jpg";
+ image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality);
+ } else {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ }
+ image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode();
+ } else {
GLTFBufferViewIndex bvi;
Ref<GLTFBufferView> bv;
@@ -3026,8 +3086,20 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
if (img_tex.is_valid()) {
image = img_tex->get_image();
}
- Error err = PNGDriverCommon::image_to_png(image, buffer);
- ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG.");
+ // Save in various image formats. Note that if the format is "None",
+ // the state's images will be empty, so this code will not be reached.
+ if (_image_save_extension.is_valid()) {
+ buffer = _image_save_extension->serialize_image_to_bytes(p_state, image, image_dict, _image_format, _lossy_quality);
+ } else if (_image_format == "PNG") {
+ buffer = image->save_png_to_buffer();
+ image_dict["mimeType"] = "image/png";
+ } else if (_image_format == "JPEG") {
+ buffer = image->save_jpg_to_buffer(_lossy_quality);
+ image_dict["mimeType"] = "image/jpeg";
+ } else {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ }
+ ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format.");
bv->byte_length = buffer.size();
p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length);
@@ -3036,27 +3108,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
p_state->buffer_views.push_back(bv);
bvi = p_state->buffer_views.size() - 1;
- d["bufferView"] = bvi;
- d["mimeType"] = "image/png";
- } else {
- ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
- String img_name = p_state->images[i]->get_name();
- if (img_name.is_empty()) {
- img_name = itos(i);
- }
- img_name = _gen_unique_name(p_state, img_name);
- img_name = img_name.pad_zeros(3) + ".png";
- String texture_dir = "textures";
- String path = p_path.get_base_dir();
- String new_texture_dir = path + "/" + texture_dir;
- Ref<DirAccess> da = DirAccess::open(path);
- if (!da->dir_exists(new_texture_dir)) {
- da->make_dir(new_texture_dir);
- }
- image->save_png(new_texture_dir.path_join(img_name));
- d["uri"] = texture_dir.path_join(img_name).uri_encode();
+ image_dict["bufferView"] = bvi;
}
- images.push_back(d);
+ images.push_back(image_dict);
}
print_verbose("Total images: " + itos(p_state->images.size()));
@@ -3069,7 +3123,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
return OK;
}
-Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index) {
+Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) {
Ref<Image> r_image;
r_image.instantiate();
// Check if any GLTFDocumentExtensions want to import this data as an image.
@@ -3078,6 +3132,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image);
ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing.");
if (!r_image->is_empty()) {
+ r_file_extension = ext->get_image_file_extension();
return r_image;
}
}
@@ -3085,8 +3140,10 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
// First we honor the mime types if they were defined.
if (p_mime_type == "image/png") { // Load buffer as PNG.
r_image->load_png_from_buffer(p_bytes);
+ r_file_extension = ".png";
} else if (p_mime_type == "image/jpeg") { // Loader buffer as JPEG.
r_image->load_jpg_from_buffer(p_bytes);
+ r_file_extension = ".jpg";
}
// If we didn't pass the above tests, we attempt loading as PNG and then JPEG directly.
// This covers URIs with base64-encoded data with application/* type but
@@ -3107,7 +3164,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
return r_image;
}
-void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image) {
+void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image) {
GLTFState::GLTFHandleBinary handling = GLTFState::GLTFHandleBinary(p_state->handle_binary_image);
if (p_image->is_empty() || handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) {
p_state->images.push_back(Ref<Texture2D>());
@@ -3124,11 +3181,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
} else {
- Error err = OK;
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() + ".png";
+ String file_path = p_state->get_base_path() + "/" + 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;
config.instantiate();
@@ -3149,8 +3206,18 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
}
}
if (must_import) {
- err = p_image->save_png(file_path);
- ERR_FAIL_COND(err != OK);
+ Error err = OK;
+ if (p_file_extension.is_empty()) {
+ // If a file extension was not specified, save the image data to a PNG file.
+ err = p_image->save_png(file_path);
+ ERR_FAIL_COND(err != OK);
+ } else {
+ // If a file extension was specified, save the original bytes to a file with that extension.
+ Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ ERR_FAIL_COND(err != OK);
+ file->store_buffer(p_bytes);
+ file->close();
+ }
// ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
HashMap<StringName, Variant> custom_options;
custom_options[SNAME("mipmaps/generate")] = true;
@@ -3300,9 +3367,10 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
continue;
}
// Parse the image data from bytes into an Image resource and save if needed.
- Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i);
+ String file_extension;
+ Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i, file_extension);
img->set_name(image_name);
- _parse_image_save_image(p_state, mime_type, i, img);
+ _parse_image_save_image(p_state, data, file_extension, i, img);
}
print_verbose("glTF: Total images: " + itos(p_state->images.size()));
@@ -3317,16 +3385,20 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
Array textures;
for (int32_t i = 0; i < p_state->textures.size(); i++) {
- Dictionary d;
- Ref<GLTFTexture> t = p_state->textures[i];
- ERR_CONTINUE(t->get_src_image() == -1);
- d["source"] = t->get_src_image();
-
- GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+ Dictionary texture_dict;
+ Ref<GLTFTexture> gltf_texture = p_state->textures[i];
+ if (_image_save_extension.is_valid()) {
+ Error err = _image_save_extension->serialize_texture_json(p_state, texture_dict, gltf_texture, _image_format);
+ ERR_FAIL_COND_V(err != OK, err);
+ } else {
+ ERR_CONTINUE(gltf_texture->get_src_image() == -1);
+ texture_dict["source"] = gltf_texture->get_src_image();
+ }
+ GLTFTextureSamplerIndex sampler_index = gltf_texture->get_sampler();
if (sampler_index != -1) {
- d["sampler"] = sampler_index;
+ texture_dict["sampler"] = sampler_index;
}
- textures.push_back(d);
+ textures.push_back(texture_dict);
}
p_state->json["textures"] = textures;
@@ -3340,28 +3412,28 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) {
const Array &textures = p_state->json["textures"];
for (GLTFTextureIndex i = 0; i < textures.size(); i++) {
- const Dictionary &dict = textures[i];
- Ref<GLTFTexture> texture;
- texture.instantiate();
+ const Dictionary &texture_dict = textures[i];
+ Ref<GLTFTexture> gltf_texture;
+ gltf_texture.instantiate();
// Check if any GLTFDocumentExtensions want to handle this texture JSON.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- Error err = ext->parse_texture_json(p_state, dict, texture);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(dict)) + " in file " + p_state->filename + ". Continuing.");
- if (texture->get_src_image() != -1) {
+ Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture);
+ ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing.");
+ if (gltf_texture->get_src_image() != -1) {
break;
}
}
- if (texture->get_src_image() == -1) {
+ if (gltf_texture->get_src_image() == -1) {
// No extensions handled it, so use the base GLTF source.
// This may be the fallback, or the only option anyway.
- ERR_FAIL_COND_V(!dict.has("source"), ERR_PARSE_ERROR);
- texture->set_src_image(dict["source"]);
+ ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR);
+ gltf_texture->set_src_image(texture_dict["source"]);
}
- if (texture->get_sampler() == -1 && dict.has("sampler")) {
- texture->set_sampler(dict["sampler"]);
+ if (gltf_texture->get_sampler() == -1 && texture_dict.has("sampler")) {
+ gltf_texture->set_sampler(texture_dict["sampler"]);
}
- p_state->textures.push_back(texture);
+ p_state->textures.push_back(gltf_texture);
}
return OK;
@@ -3387,10 +3459,11 @@ Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> p_state, const GLTFText
const GLTFImageIndex image = p_state->textures[p_texture]->get_src_image();
ERR_FAIL_INDEX_V(image, p_state->images.size(), Ref<Texture2D>());
if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) {
+ ERR_FAIL_INDEX_V(image, p_state->source_images.size(), Ref<Texture2D>());
Ref<PortableCompressedTexture2D> portable_texture;
portable_texture.instantiate();
portable_texture->set_keep_compressed_buffer(true);
- Ref<Image> new_img = p_state->source_images[p_texture]->duplicate();
+ Ref<Image> new_img = p_state->source_images[image]->duplicate();
ERR_FAIL_COND_V(new_img.is_null(), Ref<Texture2D>());
new_img->generate_mipmaps();
if (p_texture_types) {
@@ -3529,7 +3602,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
arr.push_back(c.a);
mr["baseColorFactor"] = arr;
}
- {
+ if (_image_format != "None") {
Dictionary bct;
Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
GLTFTextureIndex gltf_texture_index = -1;
@@ -3806,7 +3879,6 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
} else {
material->set_name(vformat("material_%s", itos(i)));
}
- material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
Dictionary material_extensions;
if (material_dict.has("extensions")) {
material_extensions = material_dict["extensions"];
@@ -5254,26 +5326,24 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
return OK;
}
-void GLTFDocument::_assign_scene_names(Ref<GLTFState> p_state) {
+void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) {
for (int i = 0; i < p_state->nodes.size(); i++) {
- Ref<GLTFNode> n = p_state->nodes[i];
-
+ Ref<GLTFNode> gltf_node = p_state->nodes[i];
// Any joints get unique names generated when the skeleton is made, unique to the skeleton
- if (n->skeleton >= 0) {
+ if (gltf_node->skeleton >= 0) {
continue;
}
-
- if (n->get_name().is_empty()) {
- if (n->mesh >= 0) {
- n->set_name(_gen_unique_name(p_state, "Mesh"));
- } else if (n->camera >= 0) {
- n->set_name(_gen_unique_name(p_state, "Camera3D"));
+ String gltf_node_name = gltf_node->get_name();
+ if (gltf_node_name.is_empty()) {
+ if (gltf_node->mesh >= 0) {
+ gltf_node_name = "Mesh";
+ } else if (gltf_node->camera >= 0) {
+ gltf_node_name = "Camera";
} else {
- n->set_name(_gen_unique_name(p_state, "Node"));
+ gltf_node_name = "Node";
}
}
-
- n->set_name(_gen_unique_name(p_state, n->get_name()));
+ gltf_node->set_name(_gen_unique_name(p_state, gltf_node_name));
}
}
@@ -5462,9 +5532,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
GLTFNodeIndex gltf_root = p_gltf_root;
if (gltf_root == -1) {
gltf_root = current_node_i;
- Array scenes;
- scenes.push_back(gltf_root);
- p_state->json["scene"] = scenes;
+ p_state->root_nodes.push_back(gltf_root);
}
_create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
@@ -5526,7 +5594,7 @@ void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_paren
}
void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- ERR_FAIL_COND(!p_animation_player);
+ ERR_FAIL_NULL(p_animation_player);
p_state->animation_players.push_back(p_animation_player);
print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name());
}
@@ -5545,7 +5613,7 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
}
void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
- ERR_FAIL_COND(!camera);
+ ERR_FAIL_NULL(camera);
GLTFCameraIndex camera_index = _convert_camera(p_state, camera);
if (camera_index != -1) {
p_gltf_node->camera = camera_index;
@@ -5553,7 +5621,7 @@ void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_st
}
void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
- ERR_FAIL_COND(!light);
+ ERR_FAIL_NULL(light);
GLTFLightIndex light_index = _convert_light(p_state, light);
if (light_index != -1) {
p_gltf_node->light = light_index;
@@ -5595,7 +5663,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf(
GLTFNodeIndex p_parent_node_index,
GLTFNodeIndex p_root_node_index,
Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) {
- ERR_FAIL_COND(!p_multi_mesh_instance);
+ ERR_FAIL_NULL(p_multi_mesh_instance);
Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh();
if (multi_mesh.is_null()) {
return;
@@ -5751,40 +5819,40 @@ void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent
}
}
-void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) {
- Ref<GLTFNode> gltf_node = p_state->nodes[node_index];
+void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
+ Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
if (gltf_node->skeleton >= 0) {
- _generate_skeleton_bone_node(p_state, scene_parent, scene_root, node_index);
+ _generate_skeleton_bone_node(p_state, p_node_index, p_scene_parent, p_scene_root);
return;
}
Node3D *current_node = nullptr;
// Is our parent a skeleton
- Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent);
+ Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(p_scene_parent);
const bool non_bone_parented_to_skeleton = active_skeleton;
// skinned meshes must not be placed in a bone attachment.
if (non_bone_parented_to_skeleton && gltf_node->skin < 0) {
// Bone Attachment - Parent Case
- BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, gltf_node->parent);
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent);
- scene_parent->add_child(bone_attachment, true);
- bone_attachment->set_owner(scene_root);
+ p_scene_parent->add_child(bone_attachment, true);
+ bone_attachment->set_owner(p_scene_root);
// There is no gltf_node that represent this, so just directly create a unique name
bone_attachment->set_name(gltf_node->get_name());
// We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node
// and attach it to the bone_attachment
- scene_parent = bone_attachment;
+ p_scene_parent = bone_attachment;
}
// Check if any GLTFDocumentExtension classes want to generate a node for us.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- current_node = ext->generate_scene_node(p_state, gltf_node, scene_parent);
+ current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent);
if (current_node) {
break;
}
@@ -5792,38 +5860,49 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_pare
// If none of our GLTFDocumentExtension classes generated us a node, we generate one.
if (!current_node) {
if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) {
- current_node = _generate_spatial(p_state, node_index);
- Node3D *mesh_inst = _generate_mesh_instance(p_state, node_index);
+ // GLTF specifies that skinned meshes should ignore their node transforms,
+ // only being controlled by the skeleton, so Godot will reparent a skinned
+ // mesh to its skeleton. However, we still need to ensure any child nodes
+ // keep their place in the tree, so if there are any child nodes, the skinned
+ // mesh must not be the base node, so generate an empty spatial base.
+ current_node = _generate_spatial(p_state, p_node_index);
+ Node3D *mesh_inst = _generate_mesh_instance(p_state, p_node_index);
mesh_inst->set_name(gltf_node->get_name());
-
current_node->add_child(mesh_inst, true);
} else if (gltf_node->mesh >= 0) {
- current_node = _generate_mesh_instance(p_state, node_index);
+ current_node = _generate_mesh_instance(p_state, p_node_index);
} else if (gltf_node->camera >= 0) {
- current_node = _generate_camera(p_state, node_index);
+ current_node = _generate_camera(p_state, p_node_index);
} else if (gltf_node->light >= 0) {
- current_node = _generate_light(p_state, node_index);
+ current_node = _generate_light(p_state, p_node_index);
} else {
- current_node = _generate_spatial(p_state, node_index);
+ current_node = _generate_spatial(p_state, p_node_index);
}
}
- // Add the node we generated and set the owner to the scene root.
- scene_parent->add_child(current_node, true);
- if (current_node != scene_root) {
+ String gltf_node_name = gltf_node->get_name();
+ if (!gltf_node_name.is_empty()) {
+ current_node->set_name(gltf_node_name);
+ }
+ // Note: p_scene_parent and p_scene_root must either both be null or both be valid.
+ if (p_scene_root == nullptr) {
+ // If the root node argument is null, this is the root node.
+ p_scene_root = current_node;
+ } else {
+ // Add the node we generated and set the owner to the scene root.
+ p_scene_parent->add_child(current_node, true);
Array args;
- args.append(scene_root);
+ args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
+ current_node->set_transform(gltf_node->xform);
}
- current_node->set_transform(gltf_node->xform);
- current_node->set_name(gltf_node->get_name());
- p_state->scene_nodes.insert(node_index, current_node);
+ p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, current_node, scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root);
}
}
-void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index) {
+void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
Node3D *current_node = nullptr;
@@ -5907,7 +5986,7 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, active_skeleton, p_scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], active_skeleton, p_scene_root);
}
}
@@ -6066,7 +6145,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key];
Node *root = p_animation_player->get_parent();
- ERR_FAIL_COND(root == nullptr);
+ ERR_FAIL_NULL(root);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
node_path = root->get_path_to(node_element->value);
@@ -6079,7 +6158,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (gltf_node->skeleton >= 0) {
const Skeleton3D *sk = p_state->skeletons[gltf_node->skeleton]->godot_skeleton;
- ERR_FAIL_COND(sk == nullptr);
+ ERR_FAIL_NULL(sk);
const String path = p_animation_player->get_parent()->get_path_to(sk);
const String bone = gltf_node->get_name();
@@ -6429,7 +6508,7 @@ float GLTFDocument::get_max_component(const Color &p_color) {
return MAX(MAX(r, g), b);
}
-void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) {
+void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) {
for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) {
Ref<GLTFNode> node = p_state->nodes[node_i];
@@ -6999,20 +7078,8 @@ Error GLTFDocument::_parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess
p_state->json = json.get_data();
}
- if (!p_state->json.has("asset")) {
- return ERR_PARSE_ERROR;
- }
-
- Dictionary asset = p_state->json["asset"];
-
- if (!asset.has("version")) {
- return ERR_PARSE_ERROR;
- }
-
- String version = asset["version"];
-
- p_state->major_version = version.get_slice(".", 0).to_int();
- p_state->minor_version = version.get_slice(".", 1).to_int();
+ err = _parse_asset_header(p_state);
+ ERR_FAIL_COND_V(err != OK, err);
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
@@ -7069,13 +7136,15 @@ Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
-Error GLTFDocument::_serialize_version(Ref<GLTFState> p_state) {
+Error GLTFDocument::_serialize_asset_header(Ref<GLTFState> p_state) {
const String version = "2.0";
p_state->major_version = version.get_slice(".", 0).to_int();
p_state->minor_version = version.get_slice(".", 1).to_int();
Dictionary asset;
asset["version"] = version;
-
+ if (!p_state->copyright.is_empty()) {
+ asset["copyright"] = p_state->copyright;
+ }
String hash = String(VERSION_HASH);
asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);
p_state->json["asset"] = asset;
@@ -7154,6 +7223,21 @@ void GLTFDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
&GLTFDocument::write_to_filesystem);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_SINGLE_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT);
+
+ ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format);
+ ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format);
+ ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality);
+ ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality);
+ ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode);
+ ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
+
ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
@@ -7238,7 +7322,10 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Erro
PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, PackedByteArray());
- Error err = _serialize(p_state, "");
+ // For buffers, set the state filename to an empty string, but
+ // don't touch the base path, in case the user set it manually.
+ p_state->filename = "";
+ Error err = _serialize(p_state);
ERR_FAIL_COND_V(err != OK, PackedByteArray());
PackedByteArray bytes = _serialize_glb_buffer(p_state, &err);
return bytes;
@@ -7246,7 +7333,9 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- Error err = _serialize(p_state, p_path);
+ p_state->base_path = p_path.get_base_dir();
+ p_state->filename = p_path.get_file();
+ Error err = _serialize(p_state);
if (err != OK) {
return err;
}
@@ -7257,15 +7346,40 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
return OK;
}
+Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) {
+ // Generate the skeletons and skins (if any).
+ Error err = _create_skeletons(p_state);
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons.");
+ err = _create_skins(p_state);
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins.");
+ // Generate the node tree.
+ Node *single_root;
+ if (p_state->extensions_used.has("GODOT_single_root")) {
+ _generate_scene_node(p_state, 0, nullptr, nullptr);
+ single_root = p_state->scene_nodes[0];
+ } else {
+ single_root = memnew(Node3D);
+ for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
+ _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root);
+ }
+ }
+ // Assign the scene name and single root name to each other
+ // if one is missing, or do nothing if both are already set.
+ if (unlikely(p_state->scene_name.is_empty())) {
+ p_state->scene_name = single_root->get_name();
+ } else if (single_root->get_name() == StringName()) {
+ single_root->set_name(_gen_unique_name(p_state, p_state->scene_name));
+ }
+ return single_root;
+}
+
Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
ERR_FAIL_NULL_V(p_state, nullptr);
ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr);
Error err = OK;
- GLTFNodeIndex gltf_root = p_state->root_nodes.write[0];
- Node *gltf_root_node = p_state->get_scene_node(gltf_root);
- Node *root = gltf_root_node->get_parent();
+ Node *root = _generate_scene_node_tree(p_state);
ERR_FAIL_NULL_V(root, nullptr);
- _process_mesh_instances(p_state, root);
+ _process_mesh_instances(p_state);
if (p_state->get_create_animations() && p_state->animations.size()) {
AnimationPlayer *ap = memnew(AnimationPlayer);
root->add_child(ap, true);
@@ -7301,7 +7415,11 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
ERR_FAIL_COND_V(p_state.is_null(), FAILED);
p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
-
+ if (!p_state->buffers.size()) {
+ p_state->buffers.push_back(Vector<uint8_t>());
+ }
+ // Perform export preflight for document extensions. Only extensions that
+ // return OK will be used for the rest of the export steps.
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -7310,10 +7428,21 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
document_extensions.push_back(ext);
}
}
- _convert_scene_node(p_state, p_node, -1, -1);
- if (!p_state->buffers.size()) {
- p_state->buffers.push_back(Vector<uint8_t>());
+ // Add the root node(s) and their descendants to the state.
+ if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_MULTI_ROOT) {
+ const int child_count = p_node->get_child_count();
+ if (child_count > 0) {
+ for (int i = 0; i < child_count; i++) {
+ _convert_scene_node(p_state, p_node->get_child(i), -1, -1);
+ }
+ p_state->scene_name = p_node->get_name();
+ return OK;
+ }
}
+ if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
+ p_state->extensions_used.append("GODOT_single_root");
+ }
+ _convert_scene_node(p_state, p_node, -1, -1);
return OK;
}
@@ -7338,6 +7467,23 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
return OK;
}
+Error GLTFDocument::_parse_asset_header(Ref<GLTFState> p_state) {
+ if (!p_state->json.has("asset")) {
+ return ERR_PARSE_ERROR;
+ }
+ Dictionary asset = p_state->json["asset"];
+ if (!asset.has("version")) {
+ return ERR_PARSE_ERROR;
+ }
+ String version = asset["version"];
+ p_state->major_version = version.get_slice(".", 0).to_int();
+ p_state->minor_version = version.get_slice(".", 1).to_int();
+ if (asset.has("copyright")) {
+ p_state->copyright = asset["copyright"];
+ }
+ return OK;
+}
+
Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path) {
Error err;
@@ -7399,14 +7545,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = _determine_skeletons(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
- /* CREATE SKELETONS */
- err = _create_skeletons(p_state);
- ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
-
- /* CREATE SKINS */
- err = _create_skins(p_state);
- ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
-
/* PARSE MESHES (we have enough info now) */
err = _parse_meshes(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
@@ -7424,24 +7562,19 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
/* ASSIGN SCENE NAMES */
- _assign_scene_names(p_state);
-
- Node3D *root = memnew(Node3D);
- for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
- _generate_scene_node(p_state, root, root, p_state->root_nodes[root_i]);
- }
+ _assign_node_names(p_state);
return OK;
}
-Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) {
+Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags, String p_base_path) {
// TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire
- if (r_state == Ref<GLTFState>()) {
- r_state.instantiate();
+ if (p_state == Ref<GLTFState>()) {
+ p_state.instantiate();
}
- r_state->filename = p_path.get_file().get_basename();
- r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
- r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
+ p_state->filename = p_path.get_file().get_basename();
+ p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
+ p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
@@ -7450,12 +7583,12 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
}
- r_state->base_path = base_path;
- err = _parse(r_state, base_path, file);
+ p_state->base_path = base_path;
+ err = _parse(p_state, base_path, file);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- err = ext->import_post_parse(r_state);
+ err = ext->import_post_parse(p_state);
ERR_FAIL_COND_V(err != OK, err);
}
return OK;
@@ -7493,3 +7626,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
}
return ret;
}
+
+void GLTFDocument::set_root_node_mode(GLTFDocument::RootNodeMode p_root_node_mode) {
+ _root_node_mode = p_root_node_mode;
+}
+
+GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const {
+ return _root_node_mode;
+}
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 718b05b959..828d650cff 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -40,9 +40,6 @@ class GLTFDocument : public Resource {
static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
Vector<Ref<GLTFDocumentExtension>> document_extensions;
-private:
- const float BAKE_FPS = 30.0f;
-
public:
const int32_t JOINT_GROUP_SIZE = 4;
@@ -68,6 +65,18 @@ public:
TEXTURE_TYPE_GENERIC = 0,
TEXTURE_TYPE_NORMAL = 1,
};
+ enum RootNodeMode {
+ ROOT_NODE_MODE_SINGLE_ROOT,
+ ROOT_NODE_MODE_KEEP_ROOT,
+ ROOT_NODE_MODE_MULTI_ROOT,
+ };
+
+private:
+ const float BAKE_FPS = 30.0f;
+ String _image_format = "PNG";
+ float _lossy_quality = 0.75f;
+ Ref<GLTFDocumentExtension> _image_save_extension;
+ RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;
protected:
static void _bind_methods();
@@ -77,6 +86,13 @@ public:
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
static void unregister_all_gltf_document_extensions();
+ void set_image_format(const String &p_image_format);
+ String get_image_format() const;
+ void set_lossy_quality(float p_lossy_quality);
+ float get_lossy_quality() const;
+ void set_root_node_mode(RootNodeMode p_root_node_mode);
+ RootNodeMode get_root_node_mode() const;
+
private:
void _build_parent_hierachy(Ref<GLTFState> p_state);
double _filter_number(double p_float);
@@ -149,10 +165,10 @@ private:
Error _parse_meshes(Ref<GLTFState> p_state);
Error _serialize_textures(Ref<GLTFState> p_state);
Error _serialize_texture_samplers(Ref<GLTFState> p_state);
- Error _serialize_images(Ref<GLTFState> p_state, const String &p_path);
+ Error _serialize_images(Ref<GLTFState> p_state);
Error _serialize_lights(Ref<GLTFState> p_state);
- Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index);
- void _parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image);
+ Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension);
+ void _parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image);
Error _parse_images(Ref<GLTFState> p_state, const String &p_base_path);
Error _parse_textures(Ref<GLTFState> p_state);
Error _parse_texture_samplers(Ref<GLTFState> p_state);
@@ -199,7 +215,7 @@ private:
Camera3D *_generate_camera(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
Light3D *_generate_light(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
- void _assign_scene_names(Ref<GLTFState> p_state);
+ void _assign_node_names(Ref<GLTFState> p_state);
template <class T>
T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values,
const float p_time,
@@ -272,7 +288,7 @@ private:
PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err);
Dictionary _serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material);
Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material);
- Error _serialize_version(Ref<GLTFState> p_state);
+ Error _serialize_asset_header(Ref<GLTFState> p_state);
Error _serialize_file(Ref<GLTFState> p_state, const String p_path);
Error _serialize_gltf_extensions(Ref<GLTFState> p_state) const;
@@ -293,9 +309,9 @@ private:
static float get_max_component(const Color &p_color);
public:
- Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String());
- Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0);
- Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0);
+ Error append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags = 0, String p_base_path = String());
+ Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0);
+ Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0);
public:
Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true);
@@ -304,12 +320,12 @@ public:
public:
Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path);
+ Error _parse_asset_header(Ref<GLTFState> p_state);
Error _parse_gltf_extensions(Ref<GLTFState> p_state);
- void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root);
- void _generate_scene_node(Ref<GLTFState> p_state, Node *p_scene_parent,
- Node3D *p_scene_root,
- const GLTFNodeIndex p_node_index);
- void _generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index);
+ void _process_mesh_instances(Ref<GLTFState> p_state);
+ Node *_generate_scene_node_tree(Ref<GLTFState> p_state);
+ void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
+ void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player,
const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks);
void _convert_mesh_instances(Ref<GLTFState> p_state);
@@ -367,8 +383,10 @@ public:
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
- Error _serialize(Ref<GLTFState> p_state, const String &p_path);
+ Error _serialize(Ref<GLTFState> p_state);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
+VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);
+
#endif // GLTF_DOCUMENT_H
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 372348d90d..c0ec004fd6 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -40,6 +40,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_major_version", "major_version"), &GLTFState::set_major_version);
ClassDB::bind_method(D_METHOD("get_minor_version"), &GLTFState::get_minor_version);
ClassDB::bind_method(D_METHOD("set_minor_version", "minor_version"), &GLTFState::set_minor_version);
+ ClassDB::bind_method(D_METHOD("get_copyright"), &GLTFState::get_copyright);
+ ClassDB::bind_method(D_METHOD("set_copyright", "copyright"), &GLTFState::set_copyright);
ClassDB::bind_method(D_METHOD("get_glb_data"), &GLTFState::get_glb_data);
ClassDB::bind_method(D_METHOD("set_glb_data", "glb_data"), &GLTFState::set_glb_data);
ClassDB::bind_method(D_METHOD("get_use_named_skin_binds"), &GLTFState::get_use_named_skin_binds);
@@ -62,6 +64,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scene_name", "scene_name"), &GLTFState::set_scene_name);
ClassDB::bind_method(D_METHOD("get_base_path"), &GLTFState::get_base_path);
ClassDB::bind_method(D_METHOD("set_base_path", "base_path"), &GLTFState::set_base_path);
+ ClassDB::bind_method(D_METHOD("get_filename"), &GLTFState::get_filename);
+ ClassDB::bind_method(D_METHOD("set_filename", "filename"), &GLTFState::set_filename);
ClassDB::bind_method(D_METHOD("get_root_nodes"), &GLTFState::get_root_nodes);
ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes);
ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures);
@@ -96,6 +100,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary
ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int
ADD_PROPERTY(PropertyInfo(Variant::INT, "minor_version"), "set_minor_version", "get_minor_version"); // int
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "copyright"), "set_copyright", "get_copyright"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "glb_data"), "set_glb_data", "get_glb_data"); // Vector<uint8_t>
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_named_skin_binds"), "set_use_named_skin_binds", "get_use_named_skin_binds"); // bool
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_nodes", "get_nodes"); // Vector<Ref<GLTFNode>>
@@ -106,6 +111,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_materials", "get_materials"); // Vector<Ref<Material>
ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_name"), "set_scene_name", "get_scene_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_path"), "set_base_path", "get_base_path"); // String
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename"), "set_filename", "get_filename"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_samplers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_texture_samplers", "get_texture_samplers"); //Vector<Ref<GLTFTextureSampler>>
@@ -161,6 +167,14 @@ void GLTFState::set_minor_version(int p_minor_version) {
minor_version = p_minor_version;
}
+String GLTFState::get_copyright() const {
+ return copyright;
+}
+
+void GLTFState::set_copyright(const String &p_copyright) {
+ copyright = p_copyright;
+}
+
Vector<uint8_t> GLTFState::get_glb_data() {
return glb_data;
}
@@ -370,6 +384,14 @@ void GLTFState::set_base_path(String p_base_path) {
base_path = p_base_path;
}
+String GLTFState::get_filename() const {
+ return filename;
+}
+
+void GLTFState::set_filename(const String &p_filename) {
+ filename = p_filename;
+}
+
Variant GLTFState::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index a2371b8040..91af8f91a4 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -47,11 +47,12 @@ class GLTFState : public Resource {
GDCLASS(GLTFState, Resource);
friend class GLTFDocument;
- String filename;
String base_path;
+ String filename;
Dictionary json;
int major_version = 0;
int minor_version = 0;
+ String copyright;
Vector<uint8_t> glb_data;
bool use_named_skin_binds = false;
@@ -125,6 +126,9 @@ public:
int get_minor_version();
void set_minor_version(int p_minor_version);
+ String get_copyright() const;
+ void set_copyright(const String &p_copyright);
+
Vector<uint8_t> get_glb_data();
void set_glb_data(Vector<uint8_t> p_glb_data);
@@ -167,6 +171,9 @@ public:
String get_base_path();
void set_base_path(String p_base_path);
+ String get_filename() const;
+ void set_filename(const String &p_filename);
+
PackedInt32Array get_root_nodes();
void set_root_nodes(PackedInt32Array p_root_nodes);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 1788ffac3a..fecea45fc9 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -31,6 +31,7 @@
#include "register_types.h"
#include "extensions/gltf_document_extension_convert_importer_mesh.h"
+#include "extensions/gltf_document_extension_texture_ktx.h"
#include "extensions/gltf_document_extension_texture_webp.h"
#include "extensions/gltf_spec_gloss.h"
#include "extensions/physics/gltf_document_extension_physics.h"
@@ -55,18 +56,7 @@ static void _editor_init() {
// Blend to glTF importer.
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet.
- EDITOR_DEF_RST("filesystem/import/blender/rpc_port", 6011);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT,
- "filesystem/import/blender/rpc_port", PROPERTY_HINT_RANGE, "0,65535,1"));
-
- EDITOR_DEF_RST("filesystem/import/blender/rpc_server_uptime", 5);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT,
- "filesystem/import/blender/rpc_server_uptime", PROPERTY_HINT_RANGE, "0,300,1,or_greater,suffix:s"));
-
- String blender3_path = EDITOR_DEF_RST("filesystem/import/blender/blender3_path", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,
- "filesystem/import/blender/blender3_path", PROPERTY_HINT_GLOBAL_DIR));
+ 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()) {
@@ -89,10 +79,6 @@ static void _editor_init() {
// FBX to glTF importer.
bool fbx_enabled = GLOBAL_GET("filesystem/import/fbx/enabled");
- // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet.
- String fbx2gltf_path = EDITOR_DEF_RST("filesystem/import/fbx/fbx2gltf_path", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,
- "filesystem/import/fbx/fbx2gltf_path", PROPERTY_HINT_GLOBAL_FILE));
if (fbx_enabled) {
Ref<EditorSceneFormatImporterFBX> importer;
importer.instantiate();
@@ -133,6 +119,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFTextureSampler);
// Register GLTFDocumentExtension classes with GLTFDocument.
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
+ GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureWebP);
bool is_editor = ::Engine::get_singleton()->is_editor_hint();
if (!is_editor) {
diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp
index 630b34c270..d56f67a092 100644
--- a/modules/gltf/structures/gltf_camera.cpp
+++ b/modules/gltf/structures/gltf_camera.cpp
@@ -60,7 +60,7 @@ void GLTFCamera::_bind_methods() {
Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) {
Ref<GLTFCamera> c;
c.instantiate();
- ERR_FAIL_COND_V_MSG(!p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null.");
+ ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null.");
c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
// GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees.
c->set_fov(Math::deg_to_rad(p_camera->get_fov()));
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index f9c3ca476a..3094a7bf80 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -228,6 +228,11 @@
Emitted when [member cell_size] changes.
</description>
</signal>
+ <signal name="changed">
+ <description>
+ Emitted when the [MeshLibrary] of this GridMap changes.
+ </description>
+ </signal>
</signals>
<constants>
<constant name="INVALID_CELL_ITEM" value="-1">
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index a300f7d2e2..f7c01ff840 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -32,11 +32,13 @@
#ifdef TOOLS_ENABLED
+#include "core/core_string_names.h"
#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 "scene/3d/camera_3d.h"
@@ -341,7 +343,6 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b
if (selected_palette < 0 && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) {
return false;
}
- Ref<MeshLibrary> mesh_library = node->get_mesh_library();
if (mesh_library.is_null()) {
return false;
}
@@ -743,6 +744,18 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
}
+ // Consume input to avoid conflicts with other plugins.
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ for (int i = 0; i < options->get_popup()->get_item_count(); ++i) {
+ const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i);
+ if (shortcut.is_valid() && shortcut->matches_event(p_event)) {
+ accept_event();
+ _menu_option(options->get_popup()->get_item_id(i));
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ }
+
if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) {
if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
selection.click[edit_axis]--;
@@ -855,10 +868,7 @@ void GridMapEditor::update_palette() {
mesh_library_palette->set_fixed_icon_size(Size2(min_size, min_size));
mesh_library_palette->set_max_text_lines(2);
- Ref<MeshLibrary> mesh_library = node->get_mesh_library();
-
if (mesh_library.is_null()) {
- last_mesh_library = nullptr;
search_box->set_text("");
search_box->set_editable(false);
info_message->show();
@@ -911,13 +921,39 @@ void GridMapEditor::update_palette() {
item++;
}
+}
+
+void GridMapEditor::_update_mesh_library() {
+ ERR_FAIL_NULL(node);
+
+ Ref<MeshLibrary> new_mesh_library = node->get_mesh_library();
+ if (new_mesh_library != mesh_library) {
+ if (mesh_library.is_valid()) {
+ mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ }
+ mesh_library = new_mesh_library;
+ } else {
+ return;
+ }
- last_mesh_library = *mesh_library;
+ if (mesh_library.is_valid()) {
+ mesh_library->connect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ }
+
+ update_palette();
+ // Update the cursor and grid in case the library is changed or removed.
+ _update_cursor_instance();
+ update_grid();
}
void GridMapEditor::edit(GridMap *p_gridmap) {
- if (node && node->is_connected("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids))) {
- node->disconnect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids));
+ if (node) {
+ node->disconnect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
+ node->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GridMapEditor::_update_mesh_library));
+ if (mesh_library.is_valid()) {
+ mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ mesh_library = Ref<MeshLibrary>();
+ }
}
node = p_gridmap;
@@ -950,7 +986,9 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
_draw_grids(node->get_cell_size());
update_grid();
- node->connect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids));
+ node->connect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
+ node->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GridMapEditor::_update_mesh_library));
+ _update_mesh_library();
}
void GridMapEditor::update_grid() {
@@ -1023,10 +1061,10 @@ void GridMapEditor::_draw_grids(const Vector3 &cell_size) {
}
void GridMapEditor::_update_theme() {
- options->set_icon(get_theme_icon(SNAME("GridMap"), SNAME("EditorIcons")));
- search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
- mode_thumbnail->set_icon(get_theme_icon(SNAME("FileThumbnail"), SNAME("EditorIcons")));
- mode_list->set_icon(get_theme_icon(SNAME("FileList"), SNAME("EditorIcons")));
+ options->set_icon(get_theme_icon(SNAME("GridMap"), EditorStringName(EditorIcons)));
+ search_box->set_right_icon(get_theme_icon(SNAME("Search"), EditorStringName(EditorIcons)));
+ mode_thumbnail->set_icon(get_theme_icon(SNAME("FileThumbnail"), EditorStringName(EditorIcons)));
+ mode_list->set_icon(get_theme_icon(SNAME("FileList"), EditorStringName(EditorIcons)));
}
void GridMapEditor::_notification(int p_what) {
@@ -1081,13 +1119,6 @@ void GridMapEditor::_notification(int p_what) {
}
grid_xform = xf;
}
- Ref<MeshLibrary> cgmt = node->get_mesh_library();
- if (cgmt.operator->() != last_mesh_library) {
- update_palette();
- // Update the cursor and grid in case the library is changed or removed.
- _update_cursor_instance();
- update_grid();
- }
} break;
case NOTIFICATION_THEME_CHANGED: {
@@ -1154,6 +1185,24 @@ void GridMapEditor::_bind_methods() {
}
GridMapEditor::GridMapEditor() {
+ ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true);
+ ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true);
+ ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true);
+ ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true);
+ ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_x", TTR("Cursor Back Rotate X"), KeyModifierMask::SHIFT + Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_y", TTR("Cursor Back Rotate Y"), KeyModifierMask::SHIFT + Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_z", TTR("Cursor Back Rotate Z"), KeyModifierMask::SHIFT + Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_clear_rotation", TTR("Cursor Clear Rotation"), Key::W, true);
+ ED_SHORTCUT("grid_map/paste_selects", TTR("Paste Selects"));
+ ED_SHORTCUT("grid_map/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CTRL + Key::C);
+ ED_SHORTCUT("grid_map/cut_selection", TTR("Cut Selection"), KeyModifierMask::CTRL + Key::X);
+ ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE);
+ ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F);
+
int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230);
Control *ec = memnew(Control);
ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE);
@@ -1186,29 +1235,29 @@ GridMapEditor::GridMapEditor() {
spatial_editor_hb->hide();
options->set_text(TTR("Grid Map"));
- options->get_popup()->add_item(TTR("Previous Floor"), MENU_OPTION_PREV_LEVEL, Key::Q);
- options->get_popup()->add_item(TTR("Next Floor"), MENU_OPTION_NEXT_LEVEL, Key::E);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL);
options->get_popup()->add_separator();
- options->get_popup()->add_radio_check_item(TTR("Edit X Axis"), MENU_OPTION_X_AXIS, Key::Z);
- options->get_popup()->add_radio_check_item(TTR("Edit Y Axis"), MENU_OPTION_Y_AXIS, Key::X);
- options->get_popup()->add_radio_check_item(TTR("Edit Z Axis"), MENU_OPTION_Z_AXIS, Key::C);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Cursor Rotate X"), MENU_OPTION_CURSOR_ROTATE_X, Key::A);
- options->get_popup()->add_item(TTR("Cursor Rotate Y"), MENU_OPTION_CURSOR_ROTATE_Y, Key::S);
- options->get_popup()->add_item(TTR("Cursor Rotate Z"), MENU_OPTION_CURSOR_ROTATE_Z, Key::D);
- options->get_popup()->add_item(TTR("Cursor Back Rotate X"), MENU_OPTION_CURSOR_BACK_ROTATE_X, KeyModifierMask::SHIFT + Key::A);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y, KeyModifierMask::SHIFT + Key::S);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z, KeyModifierMask::SHIFT + Key::D);
- options->get_popup()->add_item(TTR("Cursor Clear Rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION, Key::W);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
options->get_popup()->add_separator();
// TRANSLATORS: This is a toggle to select after pasting the new content.
- options->get_popup()->add_check_item(TTR("Paste Selects"), MENU_OPTION_PASTE_SELECTS);
+ options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Duplicate Selection"), MENU_OPTION_SELECTION_DUPLICATE, KeyModifierMask::CTRL + Key::C);
- options->get_popup()->add_item(TTR("Cut Selection"), MENU_OPTION_SELECTION_CUT, KeyModifierMask::CTRL + Key::X);
- options->get_popup()->add_item(TTR("Clear Selection"), MENU_OPTION_SELECTION_CLEAR, Key::KEY_DELETE);
- options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KeyModifierMask::CTRL + Key::F);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
@@ -1242,14 +1291,14 @@ GridMapEditor::GridMapEditor() {
search_box->connect("gui_input", callable_mp(this, &GridMapEditor::_sbox_input));
mode_thumbnail = memnew(Button);
- mode_thumbnail->set_flat(true);
+ mode_thumbnail->set_theme_type_variation("FlatButton");
mode_thumbnail->set_toggle_mode(true);
mode_thumbnail->set_pressed(true);
hb->add_child(mode_thumbnail);
mode_thumbnail->connect("pressed", callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_THUMBNAIL));
mode_list = memnew(Button);
- mode_list->set_flat(true);
+ mode_list->set_theme_type_variation("FlatButton");
mode_list->set_toggle_mode(true);
mode_list->set_pressed(false);
hb->add_child(mode_list);
@@ -1363,6 +1412,7 @@ GridMapEditor::GridMapEditor() {
inner_mat.instantiate();
inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2));
inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
d[RS::ARRAY_VERTEX] = triangles;
@@ -1375,11 +1425,13 @@ GridMapEditor::GridMapEditor() {
outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
selection_floor_mat.instantiate();
selection_floor_mat->set_albedo(Color(0.80, 0.80, 1.0, 1));
selection_floor_mat->set_on_top_of_alpha();
selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
d[RS::ARRAY_VERTEX] = lines;
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d);
@@ -1408,6 +1460,7 @@ GridMapEditor::GridMapEditor() {
indicator_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
indicator_mat->set_albedo(Color(0.8, 0.5, 0.1));
}
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index fd9daa7c29..924e21aef5 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -92,7 +92,7 @@ class GridMapEditor : public VBoxContainer {
List<SetItem> set_items;
GridMap *node = nullptr;
- MeshLibrary *last_mesh_library = nullptr;
+ Ref<MeshLibrary> mesh_library = nullptr;
Transform3D grid_xform;
Transform3D edit_grid_xform;
@@ -191,6 +191,7 @@ class GridMapEditor : public VBoxContainer {
void _configure();
void _menu_option(int);
void update_palette();
+ void _update_mesh_library();
void _set_display_mode(int p_mode);
void _item_selected_cbk(int idx);
void _update_cursor_transform();
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index f1e2218434..6f493f48e3 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -30,6 +30,7 @@
#include "grid_map.h"
+#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "core/object/message_queue.h"
#include "scene/3d/light_3d.h"
@@ -266,6 +267,7 @@ void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) {
}
_recreate_octant_data();
+ emit_signal(CoreStringNames::get_singleton()->changed);
}
Ref<MeshLibrary> GridMap::get_mesh_library() const {
@@ -905,13 +907,14 @@ void GridMap::_notification(int p_what) {
}
} break;
-#ifdef DEBUG_ENABLED
case NOTIFICATION_ENTER_TREE: {
+#ifdef DEBUG_ENABLED
if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
_update_navigation_debug_edge_connections();
}
- } break;
#endif // DEBUG_ENABLED
+ _update_visibility();
+ } break;
case NOTIFICATION_TRANSFORM_CHANGED: {
Transform3D new_xform = get_global_transform();
@@ -1122,6 +1125,7 @@ void GridMap::_bind_methods() {
BIND_CONSTANT(INVALID_CELL_ITEM);
ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size")));
+ ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed));
}
void GridMap::set_cell_scale(float p_scale) {
diff --git a/modules/ktx/SCsub b/modules/ktx/SCsub
new file mode 100644
index 0000000000..b160f7a287
--- /dev/null
+++ b/modules/ktx/SCsub
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_ktx = env_modules.Clone()
+
+# libktx thirdparty source files
+
+thirdparty_obj = []
+
+thirdparty_dir = "#thirdparty/libktx/"
+thirdparty_sources = [
+ "lib/basis_transcode.cpp",
+ "lib/checkheader.c",
+ "lib/filestream.c",
+ "lib/hashlist.c",
+ "lib/memstream.c",
+ "lib/swap.c",
+ "lib/texture.c",
+ "lib/texture1.c",
+ "lib/texture2.c",
+ "lib/dfdutils/createdfd.c",
+ "lib/dfdutils/colourspaces.c",
+ "lib/dfdutils/interpretdfd.c",
+ "lib/dfdutils/printdfd.c",
+ "lib/dfdutils/queries.c",
+ "lib/dfdutils/vk2dfd.c",
+]
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "include"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "utils"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "lib"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "other_include"])
+env_ktx.Prepend(CPPPATH=["#thirdparty/basis_universal"])
+
+if env["vulkan"]:
+ env_ktx.Prepend(CPPPATH=["#thirdparty/vulkan/include"])
+else:
+ # Falls back on bundled `vkformat_enum.h`.
+ env_ktx.Append(CPPDEFINES=["LIBKTX"])
+
+env_ktx.Append(CPPDEFINES=[("KHRONOS_STATIC", 1)])
+
+env_thirdparty = env_ktx.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
+env.modules_sources += thirdparty_obj
+
+# Godot source files
+module_obj = []
+
+env_ktx.add_source_files(module_obj, "*.cpp")
+env.modules_sources += module_obj
+
+# Needed to force rebuilding the module files when the thirdparty library is updated.
+env.Depends(module_obj, thirdparty_obj)
diff --git a/modules/ktx/config.py b/modules/ktx/config.py
new file mode 100644
index 0000000000..4c8391ea2a
--- /dev/null
+++ b/modules/ktx/config.py
@@ -0,0 +1,7 @@
+def can_build(env, platform):
+ env.module_add_dependencies("ktx", ["basis_universal"])
+ return True
+
+
+def configure(env):
+ pass
diff --git a/modules/denoise/register_types.cpp b/modules/ktx/register_types.cpp
index a4264b07c5..1d48e05a90 100644
--- a/modules/denoise/register_types.cpp
+++ b/modules/ktx/register_types.cpp
@@ -30,20 +30,24 @@
#include "register_types.h"
-#include "lightmap_denoiser.h"
+#include "texture_loader_ktx.h"
-#include "core/config/engine.h"
+static Ref<ResourceFormatKTX> resource_loader_ktx;
-void initialize_denoise_module(ModuleInitializationLevel p_level) {
+void initialize_ktx_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
- LightmapDenoiserOIDN::make_default_denoiser();
+ resource_loader_ktx.instantiate();
+ ResourceLoader::add_resource_format_loader(resource_loader_ktx);
}
-void uninitialize_denoise_module(ModuleInitializationLevel p_level) {
+void uninitialize_ktx_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
+
+ ResourceLoader::remove_resource_format_loader(resource_loader_ktx);
+ resource_loader_ktx.unref();
}
diff --git a/modules/denoise/register_types.h b/modules/ktx/register_types.h
index 239877a5c7..a50dc48b40 100644
--- a/modules/denoise/register_types.h
+++ b/modules/ktx/register_types.h
@@ -28,12 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DENOISE_REGISTER_TYPES_H
-#define DENOISE_REGISTER_TYPES_H
+#ifndef KTX_REGISTER_TYPES_H
+#define KTX_REGISTER_TYPES_H
#include "modules/register_module_types.h"
-void initialize_denoise_module(ModuleInitializationLevel p_level);
-void uninitialize_denoise_module(ModuleInitializationLevel p_level);
+void initialize_ktx_module(ModuleInitializationLevel p_level);
+void uninitialize_ktx_module(ModuleInitializationLevel p_level);
-#endif // DENOISE_REGISTER_TYPES_H
+#endif // KTX_REGISTER_TYPES_H
diff --git a/modules/ktx/texture_loader_ktx.cpp b/modules/ktx/texture_loader_ktx.cpp
new file mode 100644
index 0000000000..155ed56bd0
--- /dev/null
+++ b/modules/ktx/texture_loader_ktx.cpp
@@ -0,0 +1,562 @@
+/**************************************************************************/
+/* texture_loader_ktx.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 "texture_loader_ktx.h"
+
+#include "core/io/file_access.h"
+#include "core/io/file_access_memory.h"
+#include "scene/resources/image_texture.h"
+
+#include <ktx.h>
+#include <vk_format.h>
+
+KTX_error_code ktx_read(ktxStream *stream, void *dst, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->get_buffer(reinterpret_cast<uint8_t *>(dst), count);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_skip(ktxStream *stream, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ for (ktx_size_t i = 0; i < count; ++i) {
+ (*f)->get_8();
+ }
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_write(ktxStream *stream, const void *src, const ktx_size_t size, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->store_buffer(reinterpret_cast<const uint8_t *>(src), size * count);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_getpos(ktxStream *stream, ktx_off_t *const offset) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ *offset = (*f)->get_position();
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_setpos(ktxStream *stream, const ktx_off_t offset) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->seek(offset);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_getsize(ktxStream *stream, ktx_size_t *const size) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ *size = (*f)->get_length();
+ return KTX_SUCCESS;
+}
+
+void ktx_destruct(ktxStream *stream) {
+ (void)stream;
+}
+
+static Ref<Image> load_from_file_access(Ref<FileAccess> f, Error *r_error) {
+ ktxStream ktx_stream;
+ ktx_stream.read = ktx_read;
+ ktx_stream.skip = ktx_skip;
+ ktx_stream.write = ktx_write;
+ ktx_stream.getpos = ktx_getpos;
+ ktx_stream.setpos = ktx_setpos;
+ ktx_stream.getsize = ktx_getsize;
+ ktx_stream.destruct = ktx_destruct;
+ ktx_stream.type = eStreamTypeCustom;
+ ktx_stream.data.custom_ptr.address = &f;
+ ktx_stream.data.custom_ptr.allocatorAddress = NULL;
+ ktx_stream.data.custom_ptr.size = 0;
+ ktx_stream.readpos = 0;
+ ktx_stream.closeOnDestruct = false;
+ ktxTexture *ktx_texture;
+ KTX_error_code result = ktxTexture_CreateFromStream(&ktx_stream,
+ KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
+ &ktx_texture);
+ if (result != KTX_SUCCESS) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported KTX texture file.");
+ }
+
+ if (ktx_texture->numDimensions != 2) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported non-2D KTX texture file.");
+ }
+
+ if (ktx_texture->isCubemap || ktx_texture->numFaces != 1) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported cube map KTX texture file.");
+ }
+
+ if (ktx_texture->isArray) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported array KTX texture file.");
+ }
+
+ uint32_t width = ktx_texture->baseWidth;
+ uint32_t height = ktx_texture->baseHeight;
+ uint32_t mipmaps = ktx_texture->numLevels;
+ Image::Format format;
+ bool srgb = false;
+
+ switch (ktx_texture->classId) {
+ case ktxTexture1_c:
+ switch (((ktxTexture1 *)ktx_texture)->glInternalformat) {
+ case GL_LUMINANCE:
+ format = Image::FORMAT_L8;
+ break;
+ case GL_LUMINANCE_ALPHA:
+ format = Image::FORMAT_LA8;
+ break;
+ case GL_SRGB8:
+ format = Image::FORMAT_RGB8;
+ srgb = true;
+ break;
+ case GL_SRGB8_ALPHA8:
+ format = Image::FORMAT_RGBA8;
+ srgb = true;
+ break;
+ case GL_R8:
+ case GL_R8UI:
+ format = Image::FORMAT_R8;
+ break;
+ case GL_RG8:
+ format = Image::FORMAT_RG8;
+ break;
+ case GL_RGB8:
+ format = Image::FORMAT_RGB8;
+ break;
+ case GL_RGBA8:
+ format = Image::FORMAT_RGBA8;
+ break;
+ case GL_RGBA4:
+ format = Image::FORMAT_RGBA4444;
+ break;
+ case GL_RGB565:
+ format = Image::FORMAT_RGB565;
+ break;
+ case GL_R32F:
+ format = Image::FORMAT_RF;
+ break;
+ case GL_RG32F:
+ format = Image::FORMAT_RGF;
+ break;
+ case GL_RGB32F:
+ format = Image::FORMAT_RGBF;
+ break;
+ case GL_RGBA32F:
+ format = Image::FORMAT_RGBAF;
+ break;
+ case GL_R16F:
+ format = Image::FORMAT_RH;
+ break;
+ case GL_RG16F:
+ format = Image::FORMAT_RGH;
+ break;
+ case GL_RGB16F:
+ format = Image::FORMAT_RGBH;
+ break;
+ case GL_RGBA16F:
+ format = Image::FORMAT_RGBAH;
+ break;
+ case GL_RGB9_E5:
+ format = Image::FORMAT_RGBE9995;
+ break;
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ format = Image::FORMAT_DXT1;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ format = Image::FORMAT_DXT3;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ format = Image::FORMAT_DXT5;
+ break;
+ case GL_COMPRESSED_RED_RGTC1:
+ format = Image::FORMAT_RGTC_R;
+ break;
+ case GL_COMPRESSED_RG_RGTC2:
+ format = Image::FORMAT_RGTC_RG;
+ break;
+ case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
+ format = Image::FORMAT_BPTC_RGBFU;
+ break;
+ case GL_COMPRESSED_RGBA_BPTC_UNORM:
+ format = Image::FORMAT_BPTC_RGBA;
+ break;
+#if 0 // TODO: ETC compression is bogus.
+ case GL_ETC1_RGB8_OES:
+ format = Image::FORMAT_ETC;
+ break;
+ case GL_COMPRESSED_R11_EAC:
+ format = Image::FORMAT_ETC2_R11;
+ break;
+ case GL_COMPRESSED_SIGNED_R11_EAC:
+ format = Image::FORMAT_ETC2_R11S;
+ break;
+ case GL_COMPRESSED_RG11_EAC:
+ format = Image::FORMAT_ETC2_RG11;
+ break;
+ case GL_COMPRESSED_SIGNED_RG11_EAC:
+ format = Image::FORMAT_ETC2_RG11S;
+ break;
+ case GL_COMPRESSED_RGB8_ETC2:
+ format = Image::FORMAT_ETC2_RGB8;
+ break;
+ case GL_COMPRESSED_RGBA8_ETC2_EAC:
+ format = Image::FORMAT_ETC2_RGBA8;
+ break;
+ case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ format = Image::FORMAT_ETC2_RGB8A1;
+ break;
+#endif
+ case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
+ format = Image::FORMAT_ASTC_4x4;
+ break;
+ case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
+ format = Image::FORMAT_ASTC_4x4_HDR;
+ break;
+ case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
+ format = Image::FORMAT_ASTC_8x8;
+ break;
+ case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
+ format = Image::FORMAT_ASTC_8x8_HDR;
+ break;
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported format " + itos(((ktxTexture1 *)ktx_texture)->glInternalformat) + " of KTX1 texture file.");
+ }
+ break;
+ case ktxTexture2_c: {
+ ktxTexture2 *ktx_texture2 = reinterpret_cast<ktxTexture2 *>(ktx_texture);
+ if (ktx_texture2->vkFormat == VK_FORMAT_UNDEFINED) {
+ if (!ktxTexture2_NeedsTranscoding(ktx_texture2)) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid VK_FORMAT_UNDEFINED of KTX2 texture file.");
+ }
+ ktx_transcode_fmt_e ktxfmt;
+ switch (ktxTexture2_GetNumComponents(ktx_texture2)) {
+ case 1: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("rgtc")) {
+ ktxfmt = KTX_TTF_BC4_R;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 2: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("rgtc")) {
+ ktxfmt = KTX_TTF_BC5_RG;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 3: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO: srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("bptc")) {
+ ktxfmt = KTX_TTF_BC7_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("s3tc")) {
+ ktxfmt = KTX_TTF_BC1_RGB;
+ } else if (RS::get_singleton()->has_os_feature("etc")) {
+ ktxfmt = KTX_TTF_ETC1_RGB;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 4: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("astc")) {
+ ktxfmt = KTX_TTF_ASTC_4x4_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("bptc")) {
+ ktxfmt = KTX_TTF_BC7_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("s3tc")) {
+ ktxfmt = KTX_TTF_BC3_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("etc2")) {
+ ktxfmt = KTX_TTF_ETC2_RGBA;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ default: {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid components of KTX2 texture file.");
+ }
+ }
+ result = ktxTexture2_TranscodeBasis(ktx_texture2, ktxfmt, 0);
+ if (result != KTX_SUCCESS) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to transcode KTX2 texture file.");
+ }
+ }
+ switch (ktx_texture2->vkFormat) {
+ case VK_FORMAT_R8_UNORM:
+ format = Image::FORMAT_L8;
+ break;
+ case VK_FORMAT_R8G8_UNORM:
+ format = Image::FORMAT_LA8;
+ break;
+ case VK_FORMAT_R8G8B8_SRGB:
+ format = Image::FORMAT_RGB8;
+ srgb = true;
+ break;
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ format = Image::FORMAT_RGBA8;
+ srgb = true;
+ break;
+ case VK_FORMAT_R8_UINT:
+ format = Image::FORMAT_R8;
+ break;
+ case VK_FORMAT_R8G8_UINT:
+ format = Image::FORMAT_RG8;
+ break;
+ case VK_FORMAT_R8G8B8_UINT:
+ format = Image::FORMAT_RGB8;
+ break;
+ case VK_FORMAT_R8G8B8A8_UINT:
+ format = Image::FORMAT_RGBA8;
+ break;
+ case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+ format = Image::FORMAT_RGBA4444;
+ break;
+ case VK_FORMAT_R5G6B5_UNORM_PACK16:
+ format = Image::FORMAT_RGB565;
+ break;
+ case VK_FORMAT_R32_SFLOAT:
+ format = Image::FORMAT_RF;
+ break;
+ case VK_FORMAT_R32G32_SFLOAT:
+ format = Image::FORMAT_RGF;
+ break;
+ case VK_FORMAT_R32G32B32_SFLOAT:
+ format = Image::FORMAT_RGBF;
+ break;
+ case VK_FORMAT_R32G32B32A32_SFLOAT:
+ format = Image::FORMAT_RGBAF;
+ break;
+ case VK_FORMAT_R16_SFLOAT:
+ format = Image::FORMAT_RH;
+ break;
+ case VK_FORMAT_R16G16_SFLOAT:
+ format = Image::FORMAT_RGH;
+ break;
+ case VK_FORMAT_R16G16B16_SFLOAT:
+ format = Image::FORMAT_RGBH;
+ break;
+ case VK_FORMAT_R16G16B16A16_SFLOAT:
+ format = Image::FORMAT_RGBAH;
+ break;
+ case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+ format = Image::FORMAT_RGBE9995;
+ break;
+ case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+ case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+ format = Image::FORMAT_DXT1;
+ break;
+ case VK_FORMAT_BC2_UNORM_BLOCK:
+ format = Image::FORMAT_DXT3;
+ break;
+ case VK_FORMAT_BC3_UNORM_BLOCK:
+ format = Image::FORMAT_DXT5;
+ break;
+ case VK_FORMAT_BC4_UNORM_BLOCK:
+ format = Image::FORMAT_RGTC_R;
+ break;
+ case VK_FORMAT_BC5_UNORM_BLOCK:
+ format = Image::FORMAT_RGTC_RG;
+ break;
+ case VK_FORMAT_BC6H_UFLOAT_BLOCK:
+ format = Image::FORMAT_BPTC_RGBFU;
+ break;
+ case VK_FORMAT_BC6H_SFLOAT_BLOCK:
+ format = Image::FORMAT_BPTC_RGBF;
+ break;
+ case VK_FORMAT_BC7_UNORM_BLOCK:
+ format = Image::FORMAT_BPTC_RGBA;
+ break;
+#if 0 // TODO: ETC compression is bogus.
+ case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_R11;
+ break;
+ case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+ format = Image::FORMAT_ETC2_R11S;
+ break;
+ case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RG11;
+ break;
+ case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RG11S;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGB8;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGBA8;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGB8A1;
+ break;
+#endif
+ case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
+ format = Image::FORMAT_ASTC_4x4;
+ break;
+ case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
+ format = Image::FORMAT_ASTC_4x4_HDR;
+ break;
+ case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
+ format = Image::FORMAT_ASTC_8x8;
+ break;
+ case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
+ format = Image::FORMAT_ASTC_8x8_HDR;
+ break;
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported format " + itos(((ktxTexture2 *)ktx_texture)->vkFormat) + " of KTX2 texture file.");
+ break;
+ }
+ break;
+ }
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported version KTX texture file.");
+ break;
+ }
+
+ Vector<uint8_t> src_data;
+
+ // KTX use 4-bytes padding, don't use mipmaps if padding is effective
+ // TODO: unpad dynamically
+ int pixel_size = Image::get_format_pixel_size(format);
+ int pixel_rshift = Image::get_format_pixel_rshift(format);
+ int block = Image::get_format_block_size(format);
+ int minw, minh;
+ Image::get_format_min_pixel_size(format, minw, minh);
+ int w = width;
+ int h = height;
+ for (uint32_t i = 0; i < mipmaps; ++i) {
+ ktx_size_t mip_size = ktxTexture_GetImageSize(ktx_texture, i);
+ size_t bw = w % block != 0 ? w + (block - w % block) : w;
+ size_t bh = h % block != 0 ? h + (block - h % block) : h;
+ size_t s = bw * bh;
+ s *= pixel_size;
+ s >>= pixel_rshift;
+ if (mip_size != static_cast<ktx_size_t>(s)) {
+ if (!i) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported padded KTX texture file.");
+ }
+ mipmaps = 1;
+ break;
+ }
+ w = MAX(minw, w >> 1);
+ h = MAX(minh, h >> 1);
+ }
+
+ for (uint32_t i = 0; i < mipmaps; ++i) {
+ ktx_size_t mip_size = ktxTexture_GetImageSize(ktx_texture, i);
+ ktx_size_t offset;
+ if (ktxTexture_GetImageOffset(ktx_texture, i, 0, 0, &offset) != KTX_SUCCESS) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid KTX texture file.");
+ }
+ int prev_size = src_data.size();
+ src_data.resize(prev_size + mip_size);
+ memcpy(src_data.ptrw() + prev_size, ktxTexture_GetData(ktx_texture) + offset, mip_size);
+ }
+
+ Ref<Image> img = memnew(Image(width, height, mipmaps - 1, format, src_data));
+ if (srgb) {
+ img->srgb_to_linear();
+ }
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ ktxTexture_Destroy(ktx_texture);
+ return img;
+}
+
+Ref<Resource> ResourceFormatKTX::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;
+ }
+
+ Error err;
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
+ if (f.is_null()) {
+ return Ref<Resource>();
+ }
+
+ Ref<FileAccess> fref(f);
+ if (r_error) {
+ *r_error = ERR_FILE_CORRUPT;
+ }
+
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open KTX texture file '" + p_path + "'.");
+ Ref<Image> img = load_from_file_access(f, r_error);
+ Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
+ return texture;
+}
+
+static Ref<Image> _ktx_mem_loader_func(const uint8_t *p_ktx, int p_size) {
+ Ref<FileAccessMemory> f;
+ f.instantiate();
+ f->open_custom(p_ktx, p_size);
+ Error err;
+ Ref<Image> img = load_from_file_access(f, &err);
+ ERR_FAIL_COND_V(err, Ref<Image>());
+ return img;
+}
+
+void ResourceFormatKTX::get_recognized_extensions(List<String> *p_extensions) const {
+ p_extensions->push_back("ktx");
+ p_extensions->push_back("ktx2");
+}
+
+bool ResourceFormatKTX::handles_type(const String &p_type) const {
+ return ClassDB::is_parent_class(p_type, "Texture2D");
+}
+
+String ResourceFormatKTX::get_resource_type(const String &p_path) const {
+ if (p_path.get_extension().to_lower() == "ktx" || p_path.get_extension().to_lower() == "ktx2") {
+ return "ImageTexture";
+ }
+ return "";
+}
+
+ResourceFormatKTX::ResourceFormatKTX() {
+ Image::_ktx_mem_loader_func = _ktx_mem_loader_func;
+}
diff --git a/modules/dds/image_loader_dds.h b/modules/ktx/texture_loader_ktx.h
index 81cfd43551..0ea676be6b 100644
--- a/modules/dds/image_loader_dds.h
+++ b/modules/ktx/texture_loader_ktx.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* image_loader_dds.h */
+/* texture_loader_ktx.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,16 +28,21 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef IMAGE_LOADER_DDS_H
-#define IMAGE_LOADER_DDS_H
+#ifndef TEXTURE_LOADER_KTX_H
+#define TEXTURE_LOADER_KTX_H
-#include "core/io/image_loader.h"
+#include "core/io/resource_loader.h"
+#include "scene/resources/texture.h"
-class ImageLoaderDDS : public ImageFormatLoader {
+class ResourceFormatKTX : public ResourceFormatLoader {
public:
- virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale);
+ virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
- ImageLoaderDDS();
+ virtual bool handles_type(const String &p_type) const;
+ virtual String get_resource_type(const String &p_path) const;
+
+ virtual ~ResourceFormatKTX() {}
+ ResourceFormatKTX();
};
-#endif // IMAGE_LOADER_DDS_H
+#endif // TEXTURE_LOADER_KTX_H
diff --git a/modules/lightmapper_rd/config.py b/modules/lightmapper_rd/config.py
index d22f9454ed..ecc61c2d7e 100644
--- a/modules/lightmapper_rd/config.py
+++ b/modules/lightmapper_rd/config.py
@@ -1,5 +1,5 @@
def can_build(env, platform):
- return True
+ return env.editor_build and platform not in ["android", "ios"]
def configure(env):
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 748e8ae50c..556b0b4374 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -589,8 +589,12 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
raster_push_constant.grid_size[0] = grid_size;
raster_push_constant.grid_size[1] = grid_size;
raster_push_constant.grid_size[2] = grid_size;
- raster_push_constant.uv_offset[0] = 0;
- raster_push_constant.uv_offset[1] = 0;
+
+ // Half pixel offset is required so the rasterizer doesn't output face edges directly aligned into pixels.
+ // This fixes artifacts where the pixel would be traced from the edge of a face, causing half the rays to
+ // be outside of the boundaries of the geometry. See <https://github.com/godotengine/godot/issues/69126>.
+ 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);
//draw opaque
@@ -610,24 +614,28 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
}
}
-LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) {
+static Vector<RD::Uniform> dilate_or_denoise_common_uniforms(RID &p_source_light_tex, RID &p_dest_light_tex) {
Vector<RD::Uniform> uniforms;
{
- {
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
- u.binding = 0;
- u.append_id(dest_light_tex);
- uniforms.push_back(u);
- }
- {
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- u.binding = 1;
- u.append_id(source_light_tex);
- uniforms.push_back(u);
- }
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 0;
+ u.append_id(p_dest_light_tex);
+ uniforms.push_back(u);
}
+ {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 1;
+ u.append_id(p_source_light_tex);
+ uniforms.push_back(u);
+ }
+
+ return uniforms;
+}
+
+LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) {
+ Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex);
RID compute_shader_dilate = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("dilate"));
ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
@@ -663,7 +671,77 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}
-LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
+LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) {
+ RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
+ DenoiseParams denoise_params;
+ denoise_params.spatial_bandwidth = 5.0f;
+ denoise_params.light_bandwidth = p_denoiser_strength;
+ denoise_params.albedo_bandwidth = 1.0f;
+ denoise_params.normal_bandwidth = 0.1f;
+ denoise_params.filter_strength = 10.0f;
+ p_rd->buffer_update(denoise_params_buffer, 0, sizeof(DenoiseParams), &denoise_params);
+
+ Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(p_source_light_tex, p_dest_light_tex);
+ {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 2;
+ u.append_id(p_source_normal_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
+ u.binding = 3;
+ u.append_id(denoise_params_buffer);
+ uniforms.push_back(u);
+ }
+
+ RID compute_shader_denoise = p_rd->shader_create_from_spirv(p_compute_shader->get_spirv_stages("denoise"));
+ ERR_FAIL_COND_V(compute_shader_denoise.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ RID compute_shader_denoise_pipeline = p_rd->compute_pipeline_create(compute_shader_denoise);
+ RID denoise_uniform_set = p_rd->uniform_set_create(uniforms, compute_shader_denoise, 1);
+
+ // We denoise in fixed size regions and synchronize execution to avoid GPU timeouts.
+ // 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;
+ for (int s = 0; s < p_atlas_slices; s++) {
+ p_push_constant.atlas_slice = s;
+
+ for (int i = 0; i < x_regions; i++) {
+ for (int j = 0; j < y_regions; j++) {
+ int x = i * max_region_size;
+ int y = j * max_region_size;
+ int w = MIN((i + 1) * max_region_size, p_atlas_size.width) - x;
+ int h = MIN((j + 1) * max_region_size, p_atlas_size.height) - y;
+ p_push_constant.region_ofs[0] = x;
+ p_push_constant.region_ofs[1] = y;
+
+ RD::ComputeListID compute_list = p_rd->compute_list_begin();
+ p_rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_denoise_pipeline);
+ 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_end();
+
+ p_rd->submit();
+ p_rd->sync();
+ }
+ }
+ }
+
+ p_rd->free(compute_shader_denoise);
+ p_rd->free(denoise_params_buffer);
+
+ return BAKE_OK;
+}
+
+LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
if (p_step_function) {
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
}
@@ -1415,14 +1493,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
#endif
- {
- SWAP(light_accum_tex, light_accum_tex2);
- BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
- if (unlikely(error != BAKE_OK)) {
- return error;
- }
- }
-
/* DENOISE */
if (p_use_denoiser) {
@@ -1430,39 +1500,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
p_step_function(0.8, RTR("Denoising"), p_bake_userdata, true);
}
- Ref<LightmapDenoiser> denoiser = LightmapDenoiser::create();
- if (denoiser.is_valid()) {
- for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
- Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
- Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
-
- Ref<Image> denoised = denoiser->denoise_image(img);
- if (denoised != img) {
- denoised->convert(Image::FORMAT_RGBAH);
- Vector<uint8_t> ds = denoised->get_data();
- denoised.unref(); //avoid copy on write
- { //restore alpha
- uint32_t count = s.size() / 2; //uint16s
- const uint16_t *src = (const uint16_t *)s.ptr();
- uint16_t *dst = (uint16_t *)ds.ptrw();
- for (uint32_t j = 0; j < count; j += 4) {
- dst[j + 3] = src[j + 3];
- }
- }
- rd->texture_update(light_accum_tex, i, ds);
- }
- }
- }
-
{
SWAP(light_accum_tex, light_accum_tex2);
- BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
+ BakeError error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function);
if (unlikely(error != BAKE_OK)) {
return error;
}
}
}
+ {
+ SWAP(light_accum_tex, light_accum_tex2);
+ BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
+ if (unlikely(error != BAKE_OK)) {
+ return error;
+ }
+ }
+
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
@@ -1579,8 +1633,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
{
seams_push_constant.base_index = seam_offset;
rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
- seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[0].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[0].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
@@ -1603,8 +1657,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
for (int j = 1; j < uv_offset_count; j++) {
seams_push_constant.base_index = seam_offset;
- seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[j].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[j].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
index 061c9ba000..7120a21b84 100644
--- a/modules/lightmapper_rd/lightmapper_rd.h
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -229,11 +229,22 @@ class LightmapperRD : public Lightmapper {
Vector<Ref<Image>> bake_textures;
Vector<Color> probe_values;
+ struct DenoiseParams {
+ float spatial_bandwidth;
+ float light_bandwidth;
+ float albedo_bandwidth;
+ float normal_bandwidth;
+
+ float filter_strength;
+ float pad[3];
+ };
+
BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata);
void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata);
void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform);
BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
+ BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function);
public:
virtual void add_mesh(const MeshData &p_mesh) override;
@@ -241,7 +252,7 @@ public:
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_probe(const Vector3 &p_position) override;
- virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
+ virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
int get_bake_texture_count() const override;
Ref<Image> get_bake_texture(int p_index) const override;
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index c2557dfed3..ce33f2ed1d 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -5,6 +5,7 @@ secondary = "#define MODE_BOUNCE_LIGHT";
dilate = "#define MODE_DILATE";
unocclude = "#define MODE_UNOCCLUDE";
light_probes = "#define MODE_LIGHT_PROBES";
+denoise = "#define MODE_DENOISE";
#[compute]
@@ -65,11 +66,24 @@ layout(set = 1, binding = 6) uniform texture2D environment;
layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic;
#endif
-#ifdef MODE_DILATE
+#if defined(MODE_DILATE) || defined(MODE_DENOISE)
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
layout(set = 1, binding = 1) uniform texture2DArray source_light;
#endif
+#ifdef MODE_DENOISE
+layout(set = 1, binding = 2) uniform texture2DArray source_normal;
+layout(set = 1, binding = 3) uniform DenoiseParams {
+ float spatial_bandwidth;
+ float light_bandwidth;
+ float albedo_bandwidth;
+ float normal_bandwidth;
+
+ float filter_strength;
+}
+denoise_params;
+#endif
+
layout(push_constant, std430) uniform Params {
ivec2 atlas_size; // x used for light probe mode total probes
uint ray_count;
@@ -415,7 +429,7 @@ void main() {
);
for (uint j = 0; j < 4; j++) {
- sh_accum[j].rgb += light * c[j] * (1.0 / 3.0);
+ sh_accum[j].rgb += light * c[j] * 8.0;
}
#endif
@@ -735,4 +749,153 @@ void main() {
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c);
#endif
+
+#ifdef MODE_DENOISE
+ // Joint Non-local means (JNLM) denoiser.
+ //
+ // Based on YoctoImageDenoiser's JNLM implementation with corrections from "Nonlinearly Weighted First-order Regression for Denoising Monte Carlo Renderings".
+ //
+ // <https://github.com/ManuelPrandini/YoctoImageDenoiser/blob/06e19489dd64e47792acffde536393802ba48607/libs/yocto_extension/yocto_extension.cpp#L207>
+ // <https://benedikt-bitterli.me/nfor/nfor.pdf>
+ //
+ // MIT License
+ //
+ // Copyright (c) 2020 ManuelPrandini
+ //
+ // 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.
+ //
+ // Most of the constants below have been hand-picked to fit the common scenarios lightmaps
+ // are generated with, but they can be altered freely to experiment and achieve better results.
+
+ // Half the size of the patch window around each pixel that is weighted to compute the denoised pixel.
+ // A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc.
+ const int HALF_PATCH_WINDOW = 4;
+
+ // Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel.
+ const int HALF_SEARCH_WINDOW = 10;
+
+ // For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance
+ // in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also
+ // cause less features to be erased in the process.
+
+ // Controls how much the spatial distance of the pixels influences the denoising weight.
+ const float SIGMA_SPATIAL = denoise_params.spatial_bandwidth;
+
+ // Controls how much the light color distance of the pixels influences the denoising weight.
+ const float SIGMA_LIGHT = denoise_params.light_bandwidth;
+
+ // Controls how much the albedo color distance of the pixels influences the denoising weight.
+ const float SIGMA_ALBEDO = denoise_params.albedo_bandwidth;
+
+ // Controls how much the normal vector distance of the pixels influences the denoising weight.
+ const float SIGMA_NORMAL = denoise_params.normal_bandwidth;
+
+ // Strength of the filter. The original paper recommends values around 10 to 15 times the Sigma parameter.
+ const float FILTER_VALUE = denoise_params.filter_strength * SIGMA_LIGHT;
+
+ // Formula constants.
+ const int PATCH_WINDOW_DIMENSION = (HALF_PATCH_WINDOW * 2 + 1);
+ const int PATCH_WINDOW_DIMENSION_SQUARE = (PATCH_WINDOW_DIMENSION * PATCH_WINDOW_DIMENSION);
+ const float TWO_SIGMA_SPATIAL_SQUARE = 2.0f * SIGMA_SPATIAL * SIGMA_SPATIAL;
+ const float TWO_SIGMA_LIGHT_SQUARE = 2.0f * SIGMA_LIGHT * SIGMA_LIGHT;
+ const float TWO_SIGMA_ALBEDO_SQUARE = 2.0f * SIGMA_ALBEDO * SIGMA_ALBEDO;
+ const float TWO_SIGMA_NORMAL_SQUARE = 2.0f * SIGMA_NORMAL * SIGMA_NORMAL;
+ const float FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE = FILTER_VALUE * FILTER_VALUE * TWO_SIGMA_LIGHT_SQUARE;
+ const float EPSILON = 1e-6f;
+
+#ifdef USE_SH_LIGHTMAPS
+ const uint slice_count = 4;
+ const uint slice_base = params.atlas_slice * slice_count;
+#else
+ const uint slice_count = 1;
+ const uint slice_base = params.atlas_slice;
+#endif
+
+ for (uint i = 0; i < slice_count; i++) {
+ uint lightmap_slice = slice_base + i;
+ vec3 denoised_rgb = vec3(0.0f);
+ vec4 input_light = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, lightmap_slice), 0);
+ vec3 input_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
+ vec3 input_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+ if (length(input_normal) > EPSILON) {
+ // Compute the denoised pixel if the normal is valid.
+ float sum_weights = 0.0f;
+ vec3 input_rgb = input_light.rgb;
+ for (int search_y = -HALF_SEARCH_WINDOW; search_y <= HALF_SEARCH_WINDOW; search_y++) {
+ for (int search_x = -HALF_SEARCH_WINDOW; search_x <= HALF_SEARCH_WINDOW; search_x++) {
+ ivec2 search_pos = atlas_pos + ivec2(search_x, search_y);
+ vec3 search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(search_pos, lightmap_slice), 0).rgb;
+ vec3 search_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).rgb;
+ vec3 search_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).xyz;
+ float patch_square_dist = 0.0f;
+ for (int offset_y = -HALF_PATCH_WINDOW; offset_y <= HALF_PATCH_WINDOW; offset_y++) {
+ for (int offset_x = -HALF_PATCH_WINDOW; offset_x <= HALF_PATCH_WINDOW; offset_x++) {
+ ivec2 offset_input_pos = atlas_pos + ivec2(offset_x, offset_y);
+ ivec2 offset_search_pos = search_pos + ivec2(offset_x, offset_y);
+ vec3 offset_input_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_input_pos, lightmap_slice), 0).rgb;
+ vec3 offset_search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_search_pos, lightmap_slice), 0).rgb;
+ vec3 offset_delta_rgb = offset_input_rgb - offset_search_rgb;
+ patch_square_dist += dot(offset_delta_rgb, offset_delta_rgb) - TWO_SIGMA_LIGHT_SQUARE;
+ }
+ }
+
+ patch_square_dist = max(0.0f, patch_square_dist / (3.0f * PATCH_WINDOW_DIMENSION_SQUARE));
+
+ float weight = 1.0f;
+
+ // Ignore weight if search position is out of bounds.
+ weight *= step(0, search_pos.x) * step(search_pos.x, params.atlas_size.x - 1);
+ weight *= step(0, search_pos.y) * step(search_pos.y, params.atlas_size.y - 1);
+
+ // Ignore weight if normal is zero length.
+ weight *= step(EPSILON, length(search_normal));
+
+ // Weight with pixel distance.
+ vec2 pixel_delta = vec2(search_x, search_y);
+ float pixel_square_dist = dot(pixel_delta, pixel_delta);
+ weight *= exp(-pixel_square_dist / TWO_SIGMA_SPATIAL_SQUARE);
+
+ // Weight with patch.
+ weight *= exp(-patch_square_dist / FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE);
+
+ // Weight with albedo.
+ vec3 albedo_delta = input_albedo - search_albedo;
+ float albedo_square_dist = dot(albedo_delta, albedo_delta);
+ weight *= exp(-albedo_square_dist / TWO_SIGMA_ALBEDO_SQUARE);
+
+ // Weight with normal.
+ vec3 normal_delta = input_normal - search_normal;
+ float normal_square_dist = dot(normal_delta, normal_delta);
+ weight *= exp(-normal_square_dist / TWO_SIGMA_NORMAL_SQUARE);
+
+ denoised_rgb += weight * search_rgb;
+ sum_weights += weight;
+ }
+ }
+
+ denoised_rgb /= sum_weights;
+ } else {
+ // Ignore pixels where the normal is empty, just copy the light color.
+ denoised_rgb = input_light.rgb;
+ }
+
+ imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a));
+ }
+#endif
}
diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp
index 7ec4a40766..984ce88316 100644
--- a/modules/lightmapper_rd/register_types.cpp
+++ b/modules/lightmapper_rd/register_types.cpp
@@ -58,7 +58,6 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
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("rendering/lightmapping/primitive_meshes/texel_size", 0.2);
#ifndef _3D_DISABLED
GDREGISTER_CLASS(LightmapperRD);
Lightmapper::create_gpu = create_lightmapper_rd;
diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp
index 47c0dc9bb6..381ed42fe1 100644
--- a/modules/mbedtls/crypto_mbedtls.cpp
+++ b/modules/mbedtls/crypto_mbedtls.cpp
@@ -256,7 +256,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte
}
Error HMACContextMbedTLS::update(PackedByteArray p_data) {
- ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update.");
+ ERR_FAIL_NULL_V_MSG(ctx, ERR_INVALID_DATA, "Start must be called before update.");
ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_PARAMETER, "Src must not be empty.");
@@ -265,7 +265,7 @@ Error HMACContextMbedTLS::update(PackedByteArray p_data) {
}
PackedByteArray HMACContextMbedTLS::finish() {
- ERR_FAIL_COND_V_MSG(ctx == nullptr, PackedByteArray(), "Start must be called before finish.");
+ ERR_FAIL_NULL_V_MSG(ctx, PackedByteArray(), "Start must be called before finish.");
ERR_FAIL_COND_V_MSG(hash_len == 0, PackedByteArray(), "Unsupported hash type.");
PackedByteArray out;
@@ -342,7 +342,7 @@ void CryptoMbedTLS::load_default_certificates(String p_path) {
ERR_FAIL_COND(default_certs != nullptr);
default_certs = memnew(X509CertificateMbedTLS);
- ERR_FAIL_COND(default_certs == nullptr);
+ ERR_FAIL_NULL(default_certs);
if (!p_path.is_empty()) {
// Use certs defined in project settings.
@@ -419,9 +419,19 @@ Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoK
}
PackedByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) {
+ ERR_FAIL_COND_V(p_bytes < 0, PackedByteArray());
PackedByteArray out;
out.resize(p_bytes);
- mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw(), p_bytes);
+ int left = p_bytes;
+ int pos = 0;
+ // Ensure we generate random in chunks of no more than MBEDTLS_CTR_DRBG_MAX_REQUEST bytes or mbedtls_ctr_drbg_random will fail.
+ while (left > 0) {
+ int to_read = MIN(left, MBEDTLS_CTR_DRBG_MAX_REQUEST);
+ int ret = mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw() + pos, to_read);
+ ERR_FAIL_COND_V_MSG(ret != 0, PackedByteArray(), vformat("Failed to generate %d random bytes(s). Error: %d.", p_bytes, ret));
+ left -= to_read;
+ pos += to_read;
+ }
return out;
}
diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp
index ed1a97cc2c..c7373481ca 100644
--- a/modules/mbedtls/packet_peer_mbed_dtls.cpp
+++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp
@@ -40,7 +40,7 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len
PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx);
- ERR_FAIL_COND_V(sp == nullptr, 0);
+ ERR_FAIL_NULL_V(sp, 0);
Error err = sp->base->put_packet((const uint8_t *)buf, len);
if (err == ERR_BUSY) {
@@ -58,7 +58,7 @@ int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx);
- ERR_FAIL_COND_V(sp == nullptr, 0);
+ ERR_FAIL_NULL_V(sp, 0);
int pc = sp->base->get_available_packet_count();
if (pc == 0) {
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index a9d187bd64..a359b42041 100644
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -40,7 +40,7 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len)
StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx);
- ERR_FAIL_COND_V(sp == nullptr, 0);
+ ERR_FAIL_NULL_V(sp, 0);
int sent;
Error err = sp->base->put_partial_data((const uint8_t *)buf, len, sent);
@@ -60,7 +60,7 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx);
- ERR_FAIL_COND_V(sp == nullptr, 0);
+ ERR_FAIL_NULL_V(sp, 0);
int got;
Error err = sp->base->get_partial_data((uint8_t *)buf, len, got);
diff --git a/modules/minimp3/SCsub b/modules/minimp3/SCsub
index 20e3165f38..09e84f71e9 100644
--- a/modules/minimp3/SCsub
+++ b/modules/minimp3/SCsub
@@ -13,5 +13,8 @@ if not env.msvc:
else:
env_minimp3.Prepend(CPPPATH=[thirdparty_dir])
+if not env["minimp3_extra_formats"]:
+ env_minimp3.Append(CPPDEFINES=["MINIMP3_ONLY_MP3"])
+
# Godot source files
env_minimp3.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 6af86a96dc..4efa4d329e 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -28,7 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#define MINIMP3_ONLY_MP3
#define MINIMP3_FLOAT_OUTPUT
#define MINIMP3_IMPLEMENTATION
#define MINIMP3_NO_STDIO
diff --git a/modules/minimp3/config.py b/modules/minimp3/config.py
index e6bdcb2a83..115b376fc8 100644
--- a/modules/minimp3/config.py
+++ b/modules/minimp3/config.py
@@ -2,6 +2,14 @@ def can_build(env, platform):
return True
+def get_opts(platform):
+ from SCons.Variables import BoolVariable
+
+ return [
+ BoolVariable("minimp3_extra_formats", "Build minimp3 with MP1/MP2 decoding support", False),
+ ]
+
+
def configure(env):
pass
diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp
index 4e56120ec6..d60b979c3f 100644
--- a/modules/minimp3/resource_importer_mp3.cpp
+++ b/modules/minimp3/resource_importer_mp3.cpp
@@ -47,6 +47,10 @@ String ResourceImporterMP3::get_visible_name() const {
}
void ResourceImporterMP3::get_recognized_extensions(List<String> *p_extensions) const {
+#ifndef MINIMP3_ONLY_MP3
+ p_extensions->push_back("mp1");
+ p_extensions->push_back("mp2");
+#endif
p_extensions->push_back("mp3");
}
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 5cec8f41f5..e5469c4980 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -3,11 +3,11 @@ import os.path
def is_desktop(platform):
- return platform in ["windows", "macos", "linuxbsd", "uwp", "haiku"]
+ return platform in ["windows", "macos", "linuxbsd"]
def is_unix_like(platform):
- return platform in ["macos", "linuxbsd", "android", "haiku", "ios"]
+ return platform in ["macos", "linuxbsd", "android", "ios"]
def module_supports_tools_on(platform):
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index 733f1dbe34..c4aba577db 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -47,7 +47,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
for (const StringName &E : class_list) {
ClassDB::ClassInfo *t = ClassDB::classes.getptr(E);
- ERR_FAIL_COND(!t);
+ ERR_FAIL_NULL(t);
if (t->api != p_api || !t->exposed) {
continue;
}
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 2b2a8d6235..9846d60c33 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,5 +1,5 @@
-# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "haiku", "web", "ios"]
-# Eventually support for each them should be added back (except Haiku if not supported by .NET Core)
+# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "web", "ios"]
+# Eventually support for each them should be added back.
supported_platforms = ["windows", "macos", "linuxbsd", "android"]
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 1ed495943f..95bf848cbf 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -68,11 +68,9 @@
#include <stdint.h>
-#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
-
// Types that will be skipped over (in favor of their base types) when setting up instance bindings.
// This must be a superset of `ignored_types` in bindings_generator.cpp.
-const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" };
+const Vector<String> ignored_types = {};
#ifdef TOOLS_ENABLED
static bool _create_project_solution_if_needed() {
@@ -394,10 +392,6 @@ Script *CSharpLanguage::create_script() const {
return memnew(CSharpScript);
}
-bool CSharpLanguage::has_named_classes() const {
- return false;
-}
-
bool CSharpLanguage::supports_builtin_mode() const {
return false;
}
@@ -437,6 +431,11 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
return "Collections.Dictionary";
}
+ if (p_var_type_name.begins_with(Variant::get_type_name(Variant::ARRAY) + "[")) {
+ String element_type = p_var_type_name.trim_prefix(Variant::get_type_name(Variant::ARRAY) + "[").trim_suffix("]");
+ return "Collections.Array<" + variant_type_to_managed_name(element_type) + ">";
+ }
+
if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) {
return "Collections.Array";
}
@@ -551,13 +550,13 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type());
- if (!scr.is_valid() || !scr->valid || !scr->global_class) {
- // Invalid script or the script is not a global class.
- return String();
- }
+ // Always assign r_base_type and r_icon_path, even if the script
+ // is not a global one. In the case that it is not a global script,
+ // return an empty string AFTER assigning the return parameters.
+ // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp
- String name = scr->class_name;
- if (unlikely(name.is_empty())) {
+ if (!scr.is_valid() || !scr->valid) {
+ // Invalid script.
return String();
}
@@ -584,7 +583,8 @@ String CSharpLanguage::get_global_class_name(const String &p_path, String *r_bas
*r_base_type = scr->get_instance_base_type();
}
}
- return name;
+
+ return scr->global_class ? scr->class_name : String();
}
String CSharpLanguage::debug_get_error() const {
@@ -1197,8 +1197,6 @@ void CSharpLanguage::_editor_init_callback() {
// Add plugin to EditorNode and enable it
EditorNode::add_editor_plugin(godotsharp_editor);
- ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B);
- ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
@@ -1658,7 +1656,8 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
}
}
- for (const PropertyInfo &prop : props) {
+ for (PropertyInfo &prop : props) {
+ validate_property(prop);
p_properties->push_back(prop);
}
}
@@ -1687,7 +1686,7 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const {
Variant ret;
Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_property_can_revert"), args, 1, &call_error, &ret);
if (call_error.error != Callable::CallError::CALL_OK) {
return false;
@@ -1696,6 +1695,24 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const {
return (bool)ret;
}
+void CSharpInstance::validate_property(PropertyInfo &p_property) const {
+ ERR_FAIL_COND(!script.is_valid());
+
+ Variant property_arg = (Dictionary)p_property;
+ const Variant *args[1] = { &property_arg };
+
+ Variant ret;
+ Callable::CallError call_error;
+ GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
+ gchandle.get_intptr(), &SNAME("_validate_property"), args, 1, &call_error, &ret);
+
+ if (call_error.error != Callable::CallError::CALL_OK) {
+ return;
+ }
+
+ p_property = PropertyInfo::from_dict(property_arg);
+}
+
bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
ERR_FAIL_COND_V(!script.is_valid(), false);
@@ -1705,7 +1722,7 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re
Variant ret;
Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_property_get_revert"), args, 1, &call_error, &ret);
if (call_error.error != Callable::CallError::CALL_OK) {
return false;
@@ -1961,7 +1978,7 @@ const Variant CSharpInstance::get_rpc_config() const {
return script->get_rpc_config();
}
-void CSharpInstance::notification(int p_notification) {
+void CSharpInstance::notification(int p_notification, bool p_reversed) {
if (p_notification == Object::NOTIFICATION_PREDELETE) {
// When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose().
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
@@ -1979,7 +1996,7 @@ void CSharpInstance::notification(int p_notification) {
return;
}
- _call_notification(p_notification);
+ _call_notification(p_notification, p_reversed);
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose(
gchandle.get_intptr(), /* okIfNull */ false);
@@ -1987,19 +2004,17 @@ void CSharpInstance::notification(int p_notification) {
return;
}
- _call_notification(p_notification);
+ _call_notification(p_notification, p_reversed);
}
-void CSharpInstance::_call_notification(int p_notification) {
+void CSharpInstance::_call_notification(int p_notification, bool p_reversed) {
Variant arg = p_notification;
const Variant *args[1] = { &arg };
- StringName method_name = SNAME("_notification");
-
- Callable::CallError call_error;
Variant ret;
+ Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_notification"), args, 1, &call_error, &ret);
}
String CSharpInstance::to_string(bool *r_valid) {
@@ -2223,7 +2238,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
}
bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
- if (p_name == CSharpLanguage::singleton->string_names._script_source) {
+ if (p_name == SNAME("script/source")) {
r_ret = get_source_code();
return true;
}
@@ -2232,7 +2247,7 @@ bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
}
bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
- if (p_name == CSharpLanguage::singleton->string_names._script_source) {
+ if (p_name == SNAME("script/source")) {
set_source_code(p_value);
reload();
return true;
@@ -2242,7 +2257,7 @@ bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
}
void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
- p_properties->push_back(PropertyInfo(Variant::STRING, CSharpLanguage::singleton->string_names._script_source, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_properties->push_back(PropertyInfo(Variant::STRING, SNAME("script/source"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
void CSharpScript::_bind_methods() {
@@ -2282,6 +2297,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool tool = false;
bool global_class = false;
+ bool abstract_class = false;
// TODO: Use GDExtension godot_dictionary
Array methods_array;
@@ -2295,12 +2311,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
String icon_path;
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &class_name, &tool, &global_class, &icon_path,
+ p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path,
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);
p_script->class_name = class_name;
p_script->tool = tool;
p_script->global_class = global_class;
+ p_script->abstract_class = abstract_class;
p_script->icon_path = icon_path;
p_script->rpc_config.clear();
@@ -2335,6 +2352,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
mi.arguments.push_back(arg_info);
}
+ mi.flags = (uint32_t)method_info_dict["flags"];
+
p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
}
@@ -2388,7 +2407,7 @@ bool CSharpScript::can_instantiate() const {
ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
}
- return valid && extra_cond;
+ return valid && !abstract_class && extra_cond;
}
StringName CSharpScript::get_instance_base_type() const {
@@ -2584,6 +2603,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
return MethodInfo();
}
+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;
+ }
+
+ return Script::callp(p_method, p_args, p_argcount, r_error);
+}
+
Error CSharpScript::reload(bool p_keep_state) {
if (!reload_invalidated) {
return OK;
@@ -2889,9 +2920,3 @@ void ResourceFormatSaverCSharpScript::get_recognized_extensions(const Ref<Resour
bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) const {
return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr;
}
-
-CSharpLanguage::StringNameCache::StringNameCache() {
- _property_can_revert = StaticCString::create("_property_can_revert");
- _property_get_revert = StaticCString::create("_property_get_revert");
- _script_source = StaticCString::create("script/source");
-}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 9802067b46..2ab80c132d 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -63,6 +63,7 @@ class CSharpScript : public Script {
bool tool = false;
bool global_class = false;
+ bool abstract_class = false;
bool valid = false;
bool reload_invalidated = false;
@@ -164,6 +165,9 @@ public:
Vector<DocData::ClassDoc> docs;
return docs;
}
+ virtual String get_class_icon_path() const override {
+ return icon_path;
+ }
#endif // TOOLS_ENABLED
Error reload(bool p_keep_state = false) override;
@@ -185,6 +189,9 @@ public:
bool is_valid() const override {
return valid;
}
+ bool is_abstract() const override {
+ return abstract_class;
+ }
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -196,6 +203,7 @@ public:
void get_script_method_list(List<MethodInfo> *p_list) const override;
bool has_method(const StringName &p_method) const override;
MethodInfo get_method_info(const StringName &p_method) const override;
+ Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
int get_member_line(const StringName &p_member) const override;
@@ -255,6 +263,7 @@ public:
bool get(const StringName &p_name, Variant &r_ret) const override;
void get_property_list(List<PropertyInfo> *p_properties) const override;
Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override;
+ virtual void validate_property(PropertyInfo &p_property) const override;
bool property_can_revert(const StringName &p_name) const override;
bool property_get_revert(const StringName &p_name, Variant &r_ret) const override;
@@ -279,8 +288,8 @@ public:
const Variant get_rpc_config() const override;
- void notification(int p_notification) override;
- void _call_notification(int p_notification);
+ void notification(int p_notification, bool p_reversed = false) override;
+ void _call_notification(int p_notification, bool p_reversed = false);
String to_string(bool *r_valid) override;
@@ -331,14 +340,6 @@ class CSharpLanguage : public ScriptLanguage {
ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman);
- struct StringNameCache {
- StringName _property_can_revert;
- StringName _property_get_revert;
- StringName _script_source;
-
- StringNameCache();
- };
-
int lang_idx = -1;
// For debug_break and debug_break_parse
@@ -366,8 +367,6 @@ public:
static void set_instance_binding(Object *p_object, void *p_binding);
static bool has_instance_binding(Object *p_object);
- StringNameCache string_names;
-
const Mutex &get_language_bind_mutex() {
return language_bind_mutex;
}
@@ -380,10 +379,6 @@ public:
}
void set_language_index(int p_idx);
- _FORCE_INLINE_ const StringNameCache &get_string_names() {
- return string_names;
- }
-
_FORCE_INLINE_ static CSharpLanguage *get_singleton() {
return singleton;
}
@@ -434,7 +429,9 @@ public:
}
String validate_path(const String &p_path) const override;
Script *create_script() const override;
- bool has_named_classes() const override;
+#ifndef DISABLE_DEPRECATED
+ virtual bool has_named_classes() const override { return false; }
+#endif
bool supports_builtin_mode() const override;
/* TODO? */ int find_function(const String &p_function, const String &p_code) const override {
return -1;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index b0bee795f8..b35cec64f3 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -4,8 +4,6 @@
<PropertyGroup>
<!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
<GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
-
- <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
</PropertyGroup>
<PropertyGroup>
@@ -84,8 +82,6 @@
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'macos' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
- <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
- <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'ios' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'web' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
index 859ea52c93..4dcc96a1f6 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -2,11 +2,6 @@
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
<PropertyGroup>
- <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
- <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
- </PropertyGroup>
-
- <PropertyGroup>
<!--
Define constant to determine whether the real_t type in Godot is double precision or not.
By default this is false, like the official Godot builds. If someone is using a custom
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs
new file mode 100644
index 0000000000..0c374169b9
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs
@@ -0,0 +1,10 @@
+namespace Godot.SourceGenerators.Sample
+{
+ 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.Sample/OneWayProperties/AllWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs
new file mode 100644
index 0000000000..14a1802330
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Godot.SourceGenerators.Sample
+{
+ 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.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs
new file mode 100644
index 0000000000..f556bdc7e4
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs
@@ -0,0 +1,13 @@
+namespace Godot.SourceGenerators.Sample
+{
+ 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/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index b6ea4b8e88..5866db5144 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -303,11 +303,6 @@ namespace Godot.SourceGenerators
{
foreach (var property in properties)
{
- // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
- // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable.
- if (property.IsWriteOnly || property.IsReadOnly || property.SetMethod!.IsInitOnly)
- continue;
-
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
if (marshalType == null)
@@ -325,10 +320,6 @@ namespace Godot.SourceGenerators
foreach (var field in fields)
{
// TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
- // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
- if (field.IsReadOnly)
- continue;
-
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
if (marshalType == null)
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 a03c9bc06c..23879e0e53 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
@@ -18,6 +18,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!-- Do not include the generator as a lib dependency -->
<IncludeBuildOutput>false</IncludeBuildOutput>
+ <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
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 5ea0ca53c3..7b643914bb 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
@@ -125,7 +125,7 @@ namespace Godot.SourceGenerators
var members = symbol.GetMembers();
var methodSymbols = members
- .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
+ .Where(s => s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
.Cast<IMethodSymbol>()
.Where(m => m.MethodKind == MethodKind.Ordinary);
@@ -221,6 +221,29 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
+ // Generate InvokeGodotClassStaticMethod
+
+ var godotClassStaticMethods = godotClassMethods.Where(m => m.Method.IsStatic).ToArray();
+
+ if (godotClassStaticMethods.Length > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+ source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
+ source.Append(" internal new static bool InvokeGodotClassStaticMethod(in godot_string_name method, ");
+ source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n");
+
+ foreach (var method in godotClassStaticMethods)
+ {
+ GenerateMethodInvoker(method, source);
+ }
+
+ source.Append(" ret = default;\n");
+ source.Append(" return false;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
// Generate HasGodotClassMethod
if (distinctMethodNames.Length > 0)
@@ -356,7 +379,14 @@ namespace Godot.SourceGenerators
arguments = null;
}
- return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
+ MethodFlags flags = MethodFlags.Default;
+
+ if (method.Method.IsStatic)
+ {
+ flags |= MethodFlags.Static;
+ }
+
+ return new MethodInfo(method.Method.Name, returnVal, flags, arguments,
defaultArguments: null);
}
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 94d8696717..219ab7aa44 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -212,31 +212,37 @@ namespace Godot.SourceGenerators
}
// Generate GetGodotClassPropertyValue
+ bool allPropertiesAreWriteOnly = godotClassFields.Length == 0 && godotClassProperties.All(pi => pi.PropertySymbol.IsWriteOnly);
- source.Append(" /// <inheritdoc/>\n");
- source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
- source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
- source.Append("out godot_variant value)\n {\n");
-
- isFirstEntry = true;
- foreach (var property in godotClassProperties)
+ if (!allPropertiesAreWriteOnly)
{
- GeneratePropertyGetter(property.PropertySymbol.Name,
- property.PropertySymbol.Type, property.Type, source, isFirstEntry);
- isFirstEntry = false;
- }
+ source.Append(" /// <inheritdoc/>\n");
+ source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
+ source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
+ source.Append("out godot_variant value)\n {\n");
- foreach (var field in godotClassFields)
- {
- GeneratePropertyGetter(field.FieldSymbol.Name,
- field.FieldSymbol.Type, field.Type, source, isFirstEntry);
- isFirstEntry = false;
- }
+ isFirstEntry = true;
+ foreach (var property in godotClassProperties)
+ {
+ if (property.PropertySymbol.IsWriteOnly)
+ continue;
- source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
+ GeneratePropertyGetter(property.PropertySymbol.Name,
+ property.PropertySymbol.Type, property.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
- source.Append(" }\n");
+ foreach (var field in godotClassFields)
+ {
+ GeneratePropertyGetter(field.FieldSymbol.Name,
+ field.FieldSymbol.Type, field.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
+ source.Append(" }\n");
+ }
// Generate GetGodotPropertyList
const string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>";
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
index 231a7be021..9de99414b6 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
@@ -119,8 +119,14 @@ namespace Godot.SourceGenerators
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>();
- var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
- var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+ // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable.
+ var godotClassProperties = propertySymbols.Where(property => !(property.IsReadOnly || property.IsWriteOnly || property.SetMethod!.IsInitOnly))
+ .WhereIsGodotCompatibleType(typeCache)
+ .ToArray();
+ var godotClassFields = fieldSymbols.Where(property => !property.IsReadOnly)
+ .WhereIsGodotCompatibleType(typeCache)
+ .ToArray();
var signalDelegateSymbols = members
.Where(s => s.Kind == SymbolKind.NamedType)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs
new file mode 100644
index 0000000000..6e0c63dd43
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs
@@ -0,0 +1,23 @@
+#nullable enable
+
+namespace GodotTools.Build
+{
+ public class BuildDiagnostic
+ {
+ public enum DiagnosticType
+ {
+ Hidden,
+ Info,
+ Warning,
+ Error,
+ }
+
+ public DiagnosticType Type { get; set; }
+ public string? File { get; set; }
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string? Code { get; set; }
+ public string Message { get; set; } = "";
+ public string? ProjectFile { get; set; }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 312c65e364..9bb4fd153b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -40,9 +40,6 @@ namespace GodotTools.Build
plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel);
}
- public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
- public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
-
private static string GetLogFilePath(BuildInfo buildInfo)
{
return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index c083b9cc60..f9e85c36e5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -1,424 +1,150 @@
using Godot;
-using System;
-using System.Diagnostics.CodeAnalysis;
-using GodotTools.Internals;
-using File = GodotTools.Utils.File;
-using Path = System.IO.Path;
+using static GodotTools.Internals.Globals;
+
+#nullable enable
namespace GodotTools.Build
{
- public partial class BuildOutputView : VBoxContainer, ISerializationListener
+ public partial class BuildOutputView : HBoxContainer
{
- [Serializable]
- private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization
- {
- public bool Warning { get; set; }
- public string File { get; set; }
- public int Line { get; set; }
- public int Column { get; set; }
- public string Code { get; set; }
- public string Message { get; set; }
- public string ProjectFile { get; set; }
- }
-
- [Signal]
- public delegate void BuildStateChangedEventHandler();
-
- public bool HasBuildExited { get; private set; } = false;
+#nullable disable
+ private RichTextLabel _log;
- public BuildResult? BuildResult { get; private set; } = null;
+ private Button _clearButton;
+ private Button _copyButton;
+#nullable enable
- public int ErrorCount { get; private set; } = 0;
-
- public int WarningCount { get; private set; } = 0;
-
- public bool ErrorsVisible { get; set; } = true;
- public bool WarningsVisible { get; set; } = true;
-
- public Texture2D BuildStateIcon
+ public void Append(string text)
{
- get
- {
- if (!HasBuildExited)
- return GetThemeIcon("Stop", "EditorIcons");
-
- if (BuildResult == Build.BuildResult.Error)
- return GetThemeIcon("Error", "EditorIcons");
-
- if (WarningCount > 1)
- return GetThemeIcon("Warning", "EditorIcons");
-
- return null;
- }
+ _log.AddText(text);
}
- public bool LogVisible
+ public void Clear()
{
- set => _buildLog.Visible = value;
+ _log.Clear();
}
- // TODO Use List once we have proper serialization.
- private Godot.Collections.Array<BuildIssue> _issues = new();
- private ItemList _issuesList;
- private PopupMenu _issuesListContextMenu;
- private TextEdit _buildLog;
- private BuildInfo _buildInfo;
-
- private readonly object _pendingBuildLogTextLock = new object();
- [NotNull] private string _pendingBuildLogText = string.Empty;
-
- private void LoadIssuesFromFile(string csvFile)
+ private void CopyRequested()
{
- using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read);
-
- if (file == null)
- return;
+ string text = _log.GetSelectedText();
- while (!file.EofReached())
- {
- string[] csvColumns = file.GetCsvLine();
+ if (string.IsNullOrEmpty(text))
+ text = _log.GetParsedText();
- if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
- return;
-
- if (csvColumns.Length != 7)
- {
- GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
- continue;
- }
-
- var issue = new BuildIssue
- {
- Warning = csvColumns[0] == "warning",
- File = csvColumns[1],
- Line = int.Parse(csvColumns[2]),
- Column = int.Parse(csvColumns[3]),
- Code = csvColumns[4],
- Message = csvColumns[5],
- ProjectFile = csvColumns[6]
- };
-
- if (issue.Warning)
- WarningCount += 1;
- else
- ErrorCount += 1;
-
- _issues.Add(issue);
- }
+ if (!string.IsNullOrEmpty(text))
+ DisplayServer.ClipboardSet(text);
}
- private void IssueActivated(long idx)
+ public override void _Ready()
{
- if (idx < 0 || idx >= _issuesList.ItemCount)
- throw new ArgumentOutOfRangeException(nameof(idx), "Item list index out of range.");
-
- // Get correct issue idx from issue list
- int issueIndex = (int)_issuesList.GetItemMetadata((int)idx);
-
- if (issueIndex < 0 || issueIndex >= _issues.Count)
- throw new InvalidOperationException("Issue index out of range.");
-
- BuildIssue issue = _issues[issueIndex];
+ Name = "Output".TTR();
- if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
- return;
-
- string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ?
- issue.ProjectFile.GetBaseDir() :
- _buildInfo.Solution.GetBaseDir();
-
- string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
-
- if (!File.Exists(file))
- return;
-
- file = ProjectSettings.LocalizePath(file);
-
- if (file.StartsWith("res://"))
+ var vbLeft = new VBoxContainer
{
- var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
-
- if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
- Internal.EditorNodeShowScriptScreen();
- }
- }
-
- public void UpdateIssuesList()
- {
- _issuesList.Clear();
+ CustomMinimumSize = new Vector2(0, 180 * EditorScale),
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ };
+ AddChild(vbLeft);
- using (var warningIcon = GetThemeIcon("Warning", "EditorIcons"))
- using (var errorIcon = GetThemeIcon("Error", "EditorIcons"))
+ // Log - Rich Text Label.
+ _log = new RichTextLabel
{
- for (int i = 0; i < _issues.Count; i++)
- {
- BuildIssue issue = _issues[i];
-
- if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
- continue;
-
- string tooltip = string.Empty;
- tooltip += $"Message: {issue.Message}";
-
- if (!string.IsNullOrEmpty(issue.Code))
- tooltip += $"\nCode: {issue.Code}";
-
- tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
-
- string text = string.Empty;
-
- if (!string.IsNullOrEmpty(issue.File))
- {
- text += $"{issue.File}({issue.Line},{issue.Column}): ";
-
- tooltip += $"\nFile: {issue.File}";
- tooltip += $"\nLine: {issue.Line}";
- tooltip += $"\nColumn: {issue.Column}";
- }
-
- if (!string.IsNullOrEmpty(issue.ProjectFile))
- tooltip += $"\nProject: {issue.ProjectFile}";
-
- text += issue.Message;
-
- int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
- string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
- _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
-
- int index = _issuesList.ItemCount - 1;
- _issuesList.SetItemTooltip(index, tooltip);
- _issuesList.SetItemMetadata(index, i);
- }
- }
- }
-
- private void BuildLaunchFailed(BuildInfo buildInfo, string cause)
- {
- HasBuildExited = true;
- BuildResult = Build.BuildResult.Error;
-
- _issuesList.Clear();
-
- var issue = new BuildIssue { Message = cause, Warning = false };
-
- ErrorCount += 1;
- _issues.Add(issue);
-
- UpdateIssuesList();
-
- EmitSignal(nameof(BuildStateChanged));
- }
-
- private void BuildStarted(BuildInfo buildInfo)
- {
- _buildInfo = buildInfo;
- HasBuildExited = false;
-
- _issues.Clear();
- WarningCount = 0;
- ErrorCount = 0;
- _buildLog.Text = string.Empty;
-
- UpdateIssuesList();
-
- EmitSignal(nameof(BuildStateChanged));
- }
-
- private void BuildFinished(BuildResult result)
- {
- HasBuildExited = true;
- BuildResult = result;
-
- LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
+ BbcodeEnabled = true,
+ ScrollFollowing = true,
+ SelectionEnabled = true,
+ ContextMenuEnabled = true,
+ FocusMode = FocusModeEnum.Click,
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ DeselectOnFocusLossEnabled = false,
- UpdateIssuesList();
+ };
+ vbLeft.AddChild(_log);
- EmitSignal(nameof(BuildStateChanged));
- }
+ var vbRight = new VBoxContainer();
+ AddChild(vbRight);
- private void UpdateBuildLogText()
- {
- lock (_pendingBuildLogTextLock)
+ // Tools grid
+ var hbTools = new HBoxContainer
{
- _buildLog.Text += _pendingBuildLogText;
- _pendingBuildLogText = string.Empty;
- ScrollToLastNonEmptyLogLine();
- }
- }
-
- private void StdOutputReceived(string text)
- {
- lock (_pendingBuildLogTextLock)
- {
- if (_pendingBuildLogText.Length == 0)
- CallDeferred(nameof(UpdateBuildLogText));
- _pendingBuildLogText += text + "\n";
- }
- }
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ };
+ vbRight.AddChild(hbTools);
- private void StdErrorReceived(string text)
- {
- lock (_pendingBuildLogTextLock)
+ // Clear.
+ _clearButton = new Button
{
- if (_pendingBuildLogText.Length == 0)
- CallDeferred(nameof(UpdateBuildLogText));
- _pendingBuildLogText += text + "\n";
- }
- }
+ ThemeTypeVariation = "FlatButton",
+ FocusMode = FocusModeEnum.None,
+ Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K),
+ };
+ _clearButton.Pressed += Clear;
+ hbTools.AddChild(_clearButton);
- private void ScrollToLastNonEmptyLogLine()
- {
- int line;
- for (line = _buildLog.GetLineCount(); line > 0; line--)
+ // Copy.
+ _copyButton = new Button
{
- string lineText = _buildLog.GetLine(line);
-
- if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim()))
- break;
- }
-
- _buildLog.SetCaretLine(line);
- }
-
- public void RestartBuild()
- {
- if (!HasBuildExited)
- throw new InvalidOperationException("Build already started.");
-
- BuildManager.RestartBuild(this);
- }
-
- public void StopBuild()
- {
- if (!HasBuildExited)
- throw new InvalidOperationException("Build is not in progress.");
+ ThemeTypeVariation = "FlatButton",
+ FocusMode = FocusModeEnum.None,
+ Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C),
+ ShortcutContext = this,
+ };
+ _copyButton.Pressed += CopyRequested;
+ hbTools.AddChild(_copyButton);
- BuildManager.StopBuild(this);
+ UpdateTheme();
}
- private enum IssuesContextMenuOption
+ public override void _Notification(int what)
{
- Copy
- }
+ base._Notification(what);
- private void IssuesListContextOptionPressed(long id)
- {
- switch ((IssuesContextMenuOption)id)
+ if (what == NotificationThemeChanged)
{
- case IssuesContextMenuOption.Copy:
- {
- // We don't allow multi-selection but just in case that changes later...
- string text = null;
-
- foreach (int issueIndex in _issuesList.GetSelectedItems())
- {
- if (text != null)
- text += "\n";
- text += _issuesList.GetItemText(issueIndex);
- }
-
- if (text != null)
- DisplayServer.ClipboardSet(text);
- break;
- }
- default:
- throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option");
+ UpdateTheme();
}
}
- private void IssuesListClicked(long index, Vector2 atPosition, long mouseButtonIndex)
+ private void UpdateTheme()
{
- if (mouseButtonIndex != (long)MouseButton.Right)
- {
+ // Nodes will be null until _Ready is called.
+ if (_log == null)
return;
- }
-
- _ = index; // Unused
-
- _issuesListContextMenu.Clear();
- _issuesListContextMenu.Size = new Vector2I(1, 1);
-
- if (_issuesList.IsAnythingSelected())
- {
- // Add menu entries for the selected item
- _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"),
- label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy);
- }
-
- if (_issuesListContextMenu.ItemCount > 0)
- {
- _issuesListContextMenu.Position = (Vector2I)(_issuesList.GlobalPosition + atPosition);
- _issuesListContextMenu.Popup();
- }
- }
-
- public override void _Ready()
- {
- base._Ready();
- SizeFlagsVertical = SizeFlags.ExpandFill;
+ var normalFont = GetThemeFont("output_source", "EditorFonts");
+ if (normalFont != null)
+ _log.AddThemeFontOverride("normal_font", normalFont);
- var hsc = new HSplitContainer
- {
- SizeFlagsHorizontal = SizeFlags.ExpandFill,
- SizeFlagsVertical = SizeFlags.ExpandFill
- };
- AddChild(hsc);
+ var boldFont = GetThemeFont("output_source_bold", "EditorFonts");
+ if (boldFont != null)
+ _log.AddThemeFontOverride("bold_font", boldFont);
- _issuesList = new ItemList
- {
- SizeFlagsVertical = SizeFlags.ExpandFill,
- SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the build log
- };
- _issuesList.ItemActivated += IssueActivated;
- _issuesList.AllowRmbSelect = true;
- _issuesList.ItemClicked += IssuesListClicked;
- hsc.AddChild(_issuesList);
+ var italicsFont = GetThemeFont("output_source_italic", "EditorFonts");
+ if (italicsFont != null)
+ _log.AddThemeFontOverride("italics_font", italicsFont);
- _issuesListContextMenu = new PopupMenu();
- _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed;
- _issuesList.AddChild(_issuesListContextMenu);
+ var boldItalicsFont = GetThemeFont("output_source_bold_italic", "EditorFonts");
+ if (boldItalicsFont != null)
+ _log.AddThemeFontOverride("bold_italics_font", boldItalicsFont);
- _buildLog = new TextEdit
- {
- Editable = false,
- SizeFlagsVertical = SizeFlags.ExpandFill,
- SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the issues list
- };
- hsc.AddChild(_buildLog);
+ var monoFont = GetThemeFont("output_source_mono", "EditorFonts");
+ if (monoFont != null)
+ _log.AddThemeFontOverride("mono_font", monoFont);
- AddBuildEventListeners();
- }
-
- private void AddBuildEventListeners()
- {
- BuildManager.BuildLaunchFailed += BuildLaunchFailed;
- BuildManager.BuildStarted += BuildStarted;
- BuildManager.BuildFinished += BuildFinished;
- // StdOutput/Error can be received from different threads, so we need to use CallDeferred
- BuildManager.StdOutputReceived += StdOutputReceived;
- BuildManager.StdErrorReceived += StdErrorReceived;
- }
+ // Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines.
+ // This also better matches terminal output, which does not use any form of padding.
+ _log.AddThemeConstantOverride("text_highlight_h_padding", 0);
+ _log.AddThemeConstantOverride("text_highlight_v_padding", 0);
- public void OnBeforeSerialize()
- {
- // In case it didn't update yet. We don't want to have to serialize any pending output.
- UpdateBuildLogText();
-
- // NOTE:
- // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are.
- // Until that changes, we need workarounds like this one because events keep strong references to disposed objects.
- BuildManager.BuildLaunchFailed -= BuildLaunchFailed;
- BuildManager.BuildStarted -= BuildStarted;
- BuildManager.BuildFinished -= BuildFinished;
- // StdOutput/Error can be received from different threads, so we need to use CallDeferred
- BuildManager.StdOutputReceived -= StdOutputReceived;
- BuildManager.StdErrorReceived -= StdErrorReceived;
- }
+ int font_size = GetThemeFontSize("output_source_size", "EditorFonts");
+ _log.AddThemeFontSizeOverride("normal_font_size", font_size);
+ _log.AddThemeFontSizeOverride("bold_font_size", font_size);
+ _log.AddThemeFontSizeOverride("italics_font_size", font_size);
+ _log.AddThemeFontSizeOverride("mono_font_size", font_size);
- public void OnAfterDeserialize()
- {
- AddBuildEventListeners(); // Re-add them
+ _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons");
+ _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons");
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs
new file mode 100644
index 0000000000..9c165e5767
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs
@@ -0,0 +1,40 @@
+using Godot;
+
+#nullable enable
+
+namespace GodotTools.Build
+{
+ public class BuildProblemsFilter
+ {
+ public BuildDiagnostic.DiagnosticType Type { get; }
+
+ public Button ToggleButton { get; }
+
+ private int _problemsCount;
+
+ public int ProblemsCount
+ {
+ get => _problemsCount;
+ set
+ {
+ _problemsCount = value;
+ ToggleButton.Text = _problemsCount.ToString();
+ }
+ }
+
+ public bool IsActive => ToggleButton.ButtonPressed;
+
+ public BuildProblemsFilter(BuildDiagnostic.DiagnosticType type)
+ {
+ Type = type;
+ ToggleButton = new Button
+ {
+ ToggleMode = true,
+ ButtonPressed = true,
+ Text = "0",
+ FocusMode = Control.FocusModeEnum.None,
+ ThemeTypeVariation = "EditorLogFilterButton",
+ };
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
new file mode 100644
index 0000000000..b23b3f42ef
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
@@ -0,0 +1,694 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Godot;
+using GodotTools.Internals;
+using static GodotTools.Internals.Globals;
+using FileAccess = Godot.FileAccess;
+
+#nullable enable
+
+namespace GodotTools.Build
+{
+ public partial class BuildProblemsView : HBoxContainer
+ {
+#nullable disable
+ private Button _clearButton;
+ private Button _copyButton;
+
+ private Button _toggleLayoutButton;
+
+ private Button _showSearchButton;
+ private LineEdit _searchBox;
+#nullable enable
+
+ private readonly Dictionary<BuildDiagnostic.DiagnosticType, BuildProblemsFilter> _filtersByType = new();
+
+#nullable disable
+ private Tree _problemsTree;
+ private PopupMenu _problemsContextMenu;
+#nullable enable
+
+ public enum ProblemsLayout { List, Tree }
+ private ProblemsLayout _layout = ProblemsLayout.Tree;
+
+ private readonly List<BuildDiagnostic> _diagnostics = new();
+
+ public int TotalDiagnosticCount => _diagnostics.Count;
+
+ private readonly Dictionary<BuildDiagnostic.DiagnosticType, int> _problemCountByType = new();
+
+ public int WarningCount =>
+ GetProblemCountForType(BuildDiagnostic.DiagnosticType.Warning);
+
+ public int ErrorCount =>
+ GetProblemCountForType(BuildDiagnostic.DiagnosticType.Error);
+
+ private int GetProblemCountForType(BuildDiagnostic.DiagnosticType type)
+ {
+ if (!_problemCountByType.TryGetValue(type, out int count))
+ {
+ count = _diagnostics.Count(d => d.Type == type);
+ _problemCountByType[type] = count;
+ }
+
+ return count;
+ }
+
+ private static IEnumerable<BuildDiagnostic> ReadDiagnosticsFromFile(string csvFile)
+ {
+ using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read);
+
+ if (file == null)
+ yield break;
+
+ while (!file.EofReached())
+ {
+ string[] csvColumns = file.GetCsvLine();
+
+ if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
+ yield break;
+
+ if (csvColumns.Length != 7)
+ {
+ GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
+ continue;
+ }
+
+ var diagnostic = new BuildDiagnostic
+ {
+ Type = csvColumns[0] switch
+ {
+ "warning" => BuildDiagnostic.DiagnosticType.Warning,
+ "error" or _ => BuildDiagnostic.DiagnosticType.Error,
+ },
+ File = csvColumns[1],
+ Line = int.Parse(csvColumns[2]),
+ Column = int.Parse(csvColumns[3]),
+ Code = csvColumns[4],
+ Message = csvColumns[5],
+ ProjectFile = csvColumns[6],
+ };
+
+ // If there's no ProjectFile but the File is a csproj, then use that.
+ if (string.IsNullOrEmpty(diagnostic.ProjectFile) &&
+ !string.IsNullOrEmpty(diagnostic.File) &&
+ diagnostic.File.EndsWith(".csproj"))
+ {
+ diagnostic.ProjectFile = diagnostic.File;
+ }
+
+ yield return diagnostic;
+ }
+ }
+
+ public void SetDiagnosticsFromFile(string csvFile)
+ {
+ var diagnostics = ReadDiagnosticsFromFile(csvFile);
+ SetDiagnostics(diagnostics);
+ }
+
+ public void SetDiagnostics(IEnumerable<BuildDiagnostic> diagnostics)
+ {
+ _diagnostics.Clear();
+ _problemCountByType.Clear();
+
+ _diagnostics.AddRange(diagnostics);
+ UpdateProblemsView();
+ }
+
+ public void Clear()
+ {
+ _problemsTree.Clear();
+ _diagnostics.Clear();
+ _problemCountByType.Clear();
+
+ UpdateProblemsView();
+ }
+
+ private void CopySelectedProblems()
+ {
+ var selectedItem = _problemsTree.GetNextSelected(null);
+ if (selectedItem == null)
+ return;
+
+ var selectedIdxs = new List<int>();
+ while (selectedItem != null)
+ {
+ int selectedIdx = (int)selectedItem.GetMetadata(0);
+ selectedIdxs.Add(selectedIdx);
+
+ selectedItem = _problemsTree.GetNextSelected(selectedItem);
+ }
+
+ if (selectedIdxs.Count == 0)
+ return;
+
+ var selectedDiagnostics = selectedIdxs.Select(i => _diagnostics[i]);
+
+ var sb = new StringBuilder();
+
+ foreach (var diagnostic in selectedDiagnostics)
+ {
+ if (!string.IsNullOrEmpty(diagnostic.Code))
+ sb.Append($"{diagnostic.Code}: ");
+
+ sb.AppendLine($"{diagnostic.Message} {diagnostic.File}({diagnostic.Line},{diagnostic.Column})");
+ }
+
+ string text = sb.ToString();
+
+ if (!string.IsNullOrEmpty(text))
+ DisplayServer.ClipboardSet(text);
+ }
+
+ private void ToggleLayout(bool pressed)
+ {
+ _layout = pressed ? ProblemsLayout.List : ProblemsLayout.Tree;
+
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
+ editorSettings.SetSetting(GodotSharpEditor.Settings.ProblemsLayout, Variant.From(_layout));
+
+ _toggleLayoutButton.Icon = GetToggleLayoutIcon();
+ _toggleLayoutButton.TooltipText = GetToggleLayoutTooltipText();
+
+ UpdateProblemsView();
+ }
+
+ private bool GetToggleLayoutPressedState()
+ {
+ // If pressed: List layout.
+ // If not pressed: Tree layout.
+ return _layout == ProblemsLayout.List;
+ }
+
+ private Texture2D? GetToggleLayoutIcon()
+ {
+ return _layout switch
+ {
+ ProblemsLayout.List => GetThemeIcon("FileList", "EditorIcons"),
+ ProblemsLayout.Tree or _ => GetThemeIcon("FileTree", "EditorIcons"),
+ };
+ }
+
+ private string GetToggleLayoutTooltipText()
+ {
+ return _layout switch
+ {
+ ProblemsLayout.List => "View as a Tree".TTR(),
+ ProblemsLayout.Tree or _ => "View as a List".TTR(),
+ };
+ }
+
+ private void ToggleSearchBoxVisibility(bool pressed)
+ {
+ _searchBox.Visible = pressed;
+ if (pressed)
+ {
+ _searchBox.GrabFocus();
+ }
+ }
+
+ private void SearchTextChanged(string text)
+ {
+ UpdateProblemsView();
+ }
+
+ private void ToggleFilter(bool pressed)
+ {
+ UpdateProblemsView();
+ }
+
+ private void GoToSelectedProblem()
+ {
+ var selectedItem = _problemsTree.GetSelected();
+ if (selectedItem == null)
+ throw new InvalidOperationException("Item tree has no selected items.");
+
+ // Get correct diagnostic index from problems tree.
+ int diagnosticIndex = (int)selectedItem.GetMetadata(0);
+
+ if (diagnosticIndex < 0 || diagnosticIndex >= _diagnostics.Count)
+ throw new InvalidOperationException("Diagnostic index out of range.");
+
+ var diagnostic = _diagnostics[diagnosticIndex];
+
+ if (string.IsNullOrEmpty(diagnostic.ProjectFile) && string.IsNullOrEmpty(diagnostic.File))
+ return;
+
+ string? projectDir = !string.IsNullOrEmpty(diagnostic.ProjectFile) ?
+ diagnostic.ProjectFile.GetBaseDir() :
+ GodotSharpEditor.Instance.MSBuildPanel.LastBuildInfo?.Solution.GetBaseDir();
+ if (string.IsNullOrEmpty(projectDir))
+ return;
+
+ string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath());
+
+ if (!File.Exists(file))
+ return;
+
+ file = ProjectSettings.LocalizePath(file);
+
+ if (file.StartsWith("res://"))
+ {
+ var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
+
+ // Godot's ScriptEditor.Edit is 0-based but the diagnostic lines are 1-based.
+ if (script != null && Internal.ScriptEditorEdit(script, diagnostic.Line - 1, diagnostic.Column - 1))
+ Internal.EditorNodeShowScriptScreen();
+ }
+ }
+
+ private void ShowProblemContextMenu(Vector2 position, long mouseButtonIndex)
+ {
+ if (mouseButtonIndex != (long)MouseButton.Right)
+ return;
+
+ _problemsContextMenu.Clear();
+ _problemsContextMenu.Size = new Vector2I(1, 1);
+
+ var selectedItem = _problemsTree.GetSelected();
+ if (selectedItem != null)
+ {
+ // Add menu entries for the selected item.
+ _problemsContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"),
+ label: "Copy Error".TTR(), (int)ProblemContextMenuOption.Copy);
+ }
+
+ if (_problemsContextMenu.ItemCount > 0)
+ {
+ _problemsContextMenu.Position = (Vector2I)(_problemsTree.GlobalPosition + position);
+ _problemsContextMenu.Popup();
+ }
+ }
+
+ private enum ProblemContextMenuOption
+ {
+ Copy,
+ }
+
+ private void ProblemContextOptionPressed(long id)
+ {
+ switch ((ProblemContextMenuOption)id)
+ {
+ case ProblemContextMenuOption.Copy:
+ CopySelectedProblems();
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid problem context menu option.");
+ }
+ }
+
+ private bool ShouldDisplayDiagnostic(BuildDiagnostic diagnostic)
+ {
+ if (!_filtersByType[diagnostic.Type].IsActive)
+ return false;
+
+ string searchText = _searchBox.Text;
+ if (!string.IsNullOrEmpty(searchText) &&
+ (!diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
+ !(diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private Color? GetProblemItemColor(BuildDiagnostic diagnostic)
+ {
+ return diagnostic.Type switch
+ {
+ BuildDiagnostic.DiagnosticType.Warning => GetThemeColor("warning_color", "Editor"),
+ BuildDiagnostic.DiagnosticType.Error => GetThemeColor("error_color", "Editor"),
+ _ => null,
+ };
+ }
+
+ public void UpdateProblemsView()
+ {
+ switch (_layout)
+ {
+ case ProblemsLayout.List:
+ UpdateProblemsList();
+ break;
+
+ case ProblemsLayout.Tree:
+ default:
+ UpdateProblemsTree();
+ break;
+ }
+
+ foreach (var (type, filter) in _filtersByType)
+ {
+ int count = _diagnostics.Count(d => d.Type == type);
+ filter.ProblemsCount = count;
+ }
+
+ if (_diagnostics.Count == 0)
+ Name = "Problems".TTR();
+ else
+ Name = $"{"Problems".TTR()} ({_diagnostics.Count})";
+ }
+
+ private void UpdateProblemsList()
+ {
+ _problemsTree.Clear();
+
+ var root = _problemsTree.CreateItem();
+
+ for (int i = 0; i < _diagnostics.Count; i++)
+ {
+ var diagnostic = _diagnostics[i];
+
+ if (!ShouldDisplayDiagnostic(diagnostic))
+ continue;
+
+ var item = CreateProblemItem(diagnostic, includeFileInText: true);
+
+ var problemItem = _problemsTree.CreateItem(root);
+ problemItem.SetIcon(0, item.Icon);
+ problemItem.SetText(0, item.Text);
+ problemItem.SetTooltipText(0, item.TooltipText);
+ problemItem.SetMetadata(0, i);
+
+ var color = GetProblemItemColor(diagnostic);
+ if (color.HasValue)
+ problemItem.SetCustomColor(0, color.Value);
+ }
+ }
+
+ private void UpdateProblemsTree()
+ {
+ _problemsTree.Clear();
+
+ var root = _problemsTree.CreateItem();
+
+ var groupedDiagnostics = _diagnostics.Select((d, i) => (Diagnostic: d, Index: i))
+ .Where(x => ShouldDisplayDiagnostic(x.Diagnostic))
+ .GroupBy(x => x.Diagnostic.ProjectFile)
+ .Select(g => (ProjectFile: g.Key, Diagnostics: g.GroupBy(x => x.Diagnostic.File)
+ .Select(x => (File: x.Key, Diagnostics: x.ToArray()))))
+ .ToArray();
+
+ if (groupedDiagnostics.Length == 0)
+ return;
+
+ foreach (var (projectFile, projectDiagnostics) in groupedDiagnostics)
+ {
+ TreeItem projectItem;
+
+ if (groupedDiagnostics.Length == 1)
+ {
+ // Don't create a project item if there's only one project.
+ projectItem = root;
+ }
+ else
+ {
+ string projectFilePath = !string.IsNullOrEmpty(projectFile)
+ ? projectFile
+ : "Unknown project".TTR();
+ projectItem = _problemsTree.CreateItem(root);
+ projectItem.SetText(0, projectFilePath);
+ projectItem.SetSelectable(0, false);
+ }
+
+ foreach (var (file, fileDiagnostics) in projectDiagnostics)
+ {
+ if (fileDiagnostics.Length == 0)
+ continue;
+
+ string? projectDir = Path.GetDirectoryName(projectFile);
+ string relativeFilePath = !string.IsNullOrEmpty(file) && !string.IsNullOrEmpty(projectDir)
+ ? Path.GetRelativePath(projectDir, file)
+ : "Unknown file".TTR();
+
+ string fileItemText = string.Format("{0} ({1} issues)".TTR(), relativeFilePath, fileDiagnostics.Length);
+
+ var fileItem = _problemsTree.CreateItem(projectItem);
+ fileItem.SetText(0, fileItemText);
+ fileItem.SetSelectable(0, false);
+
+ foreach (var (diagnostic, index) in fileDiagnostics)
+ {
+ var item = CreateProblemItem(diagnostic);
+
+ var problemItem = _problemsTree.CreateItem(fileItem);
+ problemItem.SetIcon(0, item.Icon);
+ problemItem.SetText(0, item.Text);
+ problemItem.SetTooltipText(0, item.TooltipText);
+ problemItem.SetMetadata(0, index);
+
+ var color = GetProblemItemColor(diagnostic);
+ if (color.HasValue)
+ problemItem.SetCustomColor(0, color.Value);
+ }
+ }
+ }
+ }
+
+ private class ProblemItem
+ {
+ public string? Text { get; set; }
+ public string? TooltipText { get; set; }
+ public Texture2D? Icon { get; set; }
+ }
+
+ private ProblemItem CreateProblemItem(BuildDiagnostic diagnostic, bool includeFileInText = false)
+ {
+ var text = new StringBuilder();
+ var tooltip = new StringBuilder();
+
+ ReadOnlySpan<char> shortMessage = diagnostic.Message.AsSpan();
+ int lineBreakIdx = shortMessage.IndexOf('\n');
+ if (lineBreakIdx != -1)
+ shortMessage = shortMessage[..lineBreakIdx];
+ text.Append(shortMessage);
+
+ tooltip.Append($"Message: {diagnostic.Message}");
+
+ if (!string.IsNullOrEmpty(diagnostic.Code))
+ tooltip.Append($"\nCode: {diagnostic.Code}");
+
+ string type = diagnostic.Type switch
+ {
+ BuildDiagnostic.DiagnosticType.Hidden => "hidden",
+ BuildDiagnostic.DiagnosticType.Info => "info",
+ BuildDiagnostic.DiagnosticType.Warning => "warning",
+ BuildDiagnostic.DiagnosticType.Error => "error",
+ _ => "unknown",
+ };
+ tooltip.Append($"\nType: {type}");
+
+ if (!string.IsNullOrEmpty(diagnostic.File))
+ {
+ text.Append(' ');
+ if (includeFileInText)
+ {
+ text.Append(diagnostic.File);
+ }
+
+ text.Append($"({diagnostic.Line},{diagnostic.Column})");
+
+ tooltip.Append($"\nFile: {diagnostic.File}");
+ tooltip.Append($"\nLine: {diagnostic.Line}");
+ tooltip.Append($"\nColumn: {diagnostic.Column}");
+ }
+
+ if (!string.IsNullOrEmpty(diagnostic.ProjectFile))
+ tooltip.Append($"\nProject: {diagnostic.ProjectFile}");
+
+ return new ProblemItem()
+ {
+ Text = text.ToString(),
+ TooltipText = tooltip.ToString(),
+ Icon = diagnostic.Type switch
+ {
+ BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("Warning", "EditorIcons"),
+ BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("Error", "EditorIcons"),
+ _ => null,
+ },
+ };
+ }
+
+ public override void _Ready()
+ {
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>();
+
+ Name = "Problems".TTR();
+
+ var vbLeft = new VBoxContainer
+ {
+ CustomMinimumSize = new Vector2(0, 180 * EditorScale),
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ };
+ AddChild(vbLeft);
+
+ // Problem Tree.
+ _problemsTree = new Tree
+ {
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ AllowRmbSelect = true,
+ HideRoot = true,
+ };
+ _problemsTree.ItemActivated += GoToSelectedProblem;
+ _problemsTree.ItemMouseSelected += ShowProblemContextMenu;
+ vbLeft.AddChild(_problemsTree);
+
+ // Problem context menu.
+ _problemsContextMenu = new PopupMenu();
+ _problemsContextMenu.IdPressed += ProblemContextOptionPressed;
+ _problemsTree.AddChild(_problemsContextMenu);
+
+ // Search box.
+ _searchBox = new LineEdit
+ {
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ PlaceholderText = "Filter Problems".TTR(),
+ ClearButtonEnabled = true,
+ };
+ _searchBox.TextChanged += SearchTextChanged;
+ vbLeft.AddChild(_searchBox);
+
+ var vbRight = new VBoxContainer();
+ AddChild(vbRight);
+
+ // Tools grid.
+ var hbTools = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ };
+ vbRight.AddChild(hbTools);
+
+ // Clear.
+ _clearButton = new Button
+ {
+ ThemeTypeVariation = "FlatButton",
+ FocusMode = FocusModeEnum.None,
+ Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K),
+ ShortcutContext = this,
+ };
+ _clearButton.Pressed += Clear;
+ hbTools.AddChild(_clearButton);
+
+ // Copy.
+ _copyButton = new Button
+ {
+ ThemeTypeVariation = "FlatButton",
+ FocusMode = FocusModeEnum.None,
+ Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C),
+ ShortcutContext = this,
+ };
+ _copyButton.Pressed += CopySelectedProblems;
+ hbTools.AddChild(_copyButton);
+
+ // A second hbox to make a 2x2 grid of buttons.
+ var hbTools2 = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
+ };
+ vbRight.AddChild(hbTools2);
+
+ // Toggle List/Tree.
+ _toggleLayoutButton = new Button
+ {
+ Flat = true,
+ FocusMode = FocusModeEnum.None,
+ TooltipText = GetToggleLayoutTooltipText(),
+ ToggleMode = true,
+ ButtonPressed = GetToggleLayoutPressedState(),
+ };
+ // Don't tint the icon even when in "pressed" state.
+ _toggleLayoutButton.AddThemeColorOverride("icon_pressed_color", Colors.White);
+ _toggleLayoutButton.Toggled += ToggleLayout;
+ hbTools2.AddChild(_toggleLayoutButton);
+
+ // Show Search.
+ _showSearchButton = new Button
+ {
+ ThemeTypeVariation = "FlatButton",
+ FocusMode = FocusModeEnum.None,
+ ToggleMode = true,
+ ButtonPressed = true,
+ Shortcut = EditorDefShortcut("editor/open_search", "Focus Search/Filter Bar".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.F),
+ ShortcutContext = this,
+ };
+ _showSearchButton.Toggled += ToggleSearchBoxVisibility;
+ hbTools2.AddChild(_showSearchButton);
+
+ // Diagnostic Type Filters.
+ vbRight.AddChild(new HSeparator());
+
+ var infoFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Info);
+ infoFilter.ToggleButton.TooltipText = "Toggle visibility of info diagnostics.".TTR();
+ infoFilter.ToggleButton.Toggled += ToggleFilter;
+ vbRight.AddChild(infoFilter.ToggleButton);
+ _filtersByType[BuildDiagnostic.DiagnosticType.Info] = infoFilter;
+
+ var errorFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Error);
+ errorFilter.ToggleButton.TooltipText = "Toggle visibility of errors.".TTR();
+ errorFilter.ToggleButton.Toggled += ToggleFilter;
+ vbRight.AddChild(errorFilter.ToggleButton);
+ _filtersByType[BuildDiagnostic.DiagnosticType.Error] = errorFilter;
+
+ var warningFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Warning);
+ warningFilter.ToggleButton.TooltipText = "Toggle visibility of warnings.".TTR();
+ warningFilter.ToggleButton.Toggled += ToggleFilter;
+ vbRight.AddChild(warningFilter.ToggleButton);
+ _filtersByType[BuildDiagnostic.DiagnosticType.Warning] = warningFilter;
+
+ UpdateTheme();
+
+ UpdateProblemsView();
+ }
+
+ public override void _Notification(int what)
+ {
+ base._Notification(what);
+
+ switch ((long)what)
+ {
+ case EditorSettings.NotificationEditorSettingsChanged:
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>();
+ _toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState();
+ UpdateProblemsView();
+ break;
+
+ case NotificationThemeChanged:
+ UpdateTheme();
+ break;
+ }
+ }
+
+ private void UpdateTheme()
+ {
+ // Nodes will be null until _Ready is called.
+ if (_clearButton == null)
+ return;
+
+ foreach (var (type, filter) in _filtersByType)
+ {
+ filter.ToggleButton.Icon = type switch
+ {
+ BuildDiagnostic.DiagnosticType.Info => GetThemeIcon("Popup", "EditorIcons"),
+ BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("StatusWarning", "EditorIcons"),
+ BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("StatusError", "EditorIcons"),
+ _ => null,
+ };
+ }
+
+ _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons");
+ _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons");
+ _toggleLayoutButton.Icon = GetToggleLayoutIcon();
+ _showSearchButton.Icon = GetThemeIcon("Search", "EditorIcons");
+ _searchBox.RightIcon = GetThemeIcon("Search", "EditorIcons");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index d7d484d166..8a292fd73a 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -23,7 +23,7 @@ namespace GodotTools.Build
if (dotnetPath == null)
throw new FileNotFoundException("Cannot find the dotnet executable.");
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
var startInfo = new ProcessStartInfo(dotnetPath);
@@ -94,7 +94,7 @@ namespace GodotTools.Build
if (dotnetPath == null)
throw new FileNotFoundException("Cannot find the dotnet executable.");
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
var startInfo = new ProcessStartInfo(dotnetPath);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index 1bb1b3227e..bae87dd1dd 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -5,28 +5,73 @@ using GodotTools.Internals;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
+#nullable enable
+
namespace GodotTools.Build
{
- public partial class MSBuildPanel : VBoxContainer
+ public partial class MSBuildPanel : MarginContainer, ISerializationListener
{
- public BuildOutputView BuildOutputView { get; private set; }
+ [Signal]
+ public delegate void BuildStateChangedEventHandler();
+
+#nullable disable
+ private MenuButton _buildMenuButton;
+ private Button _openLogsFolderButton;
+
+ private BuildProblemsView _problemsView;
+ private BuildOutputView _outputView;
+#nullable enable
+
+ public BuildInfo? LastBuildInfo { get; private set; }
+ public bool IsBuildingOngoing { get; private set; }
+ public BuildResult? BuildResult { get; private set; }
- private MenuButton _buildMenuBtn;
- private Button _errorsBtn;
- private Button _warningsBtn;
- private Button _viewLogBtn;
- private Button _openLogsFolderBtn;
+ private readonly object _pendingBuildLogTextLock = new object();
+ private string _pendingBuildLogText = string.Empty;
- private void WarningsToggled(bool pressed)
+ public Texture2D? GetBuildStateIcon()
{
- BuildOutputView.WarningsVisible = pressed;
- BuildOutputView.UpdateIssuesList();
+ if (IsBuildingOngoing)
+ return GetThemeIcon("Stop", "EditorIcons");
+
+ if (_problemsView.WarningCount > 0 && _problemsView.ErrorCount > 0)
+ return GetThemeIcon("ErrorWarning", "EditorIcons");
+
+ if (_problemsView.WarningCount > 0)
+ return GetThemeIcon("Warning", "EditorIcons");
+
+ if (_problemsView.ErrorCount > 0)
+ return GetThemeIcon("Error", "EditorIcons");
+
+ return null;
}
- private void ErrorsToggled(bool pressed)
+ private enum BuildMenuOptions
{
- BuildOutputView.ErrorsVisible = pressed;
- BuildOutputView.UpdateIssuesList();
+ BuildProject,
+ RebuildProject,
+ CleanProject,
+ }
+
+ private void BuildMenuOptionPressed(long id)
+ {
+ switch ((BuildMenuOptions)id)
+ {
+ case BuildMenuOptions.BuildProject:
+ BuildProject();
+ break;
+
+ case BuildMenuOptions.RebuildProject:
+ RebuildProject();
+ break;
+
+ case BuildMenuOptions.CleanProject:
+ CleanProject();
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option");
+ }
}
public void BuildProject()
@@ -73,108 +118,136 @@ namespace GodotTools.Build
_ = BuildManager.CleanProjectBlocking("Debug");
}
- private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed;
-
- private void OpenLogsFolderPressed() => OS.ShellOpen(
+ private void OpenLogsFolder() => OS.ShellOpen(
$"file://{GodotSharpDirs.LogsDirPathFor("Debug")}"
);
- private void BuildMenuOptionPressed(long id)
+ private void BuildLaunchFailed(BuildInfo buildInfo, string cause)
{
- switch ((BuildMenuOptions)id)
+ IsBuildingOngoing = false;
+ BuildResult = Build.BuildResult.Error;
+
+ _problemsView.Clear();
+ _outputView.Clear();
+
+ var diagnostic = new BuildDiagnostic
{
- case BuildMenuOptions.BuildProject:
- BuildProject();
- break;
- case BuildMenuOptions.RebuildProject:
- RebuildProject();
- break;
- case BuildMenuOptions.CleanProject:
- CleanProject();
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option");
+ Type = BuildDiagnostic.DiagnosticType.Error,
+ Message = cause,
+ };
+
+ _problemsView.SetDiagnostics(new[] { diagnostic });
+
+ EmitSignal(SignalName.BuildStateChanged);
+ }
+
+ private void BuildStarted(BuildInfo buildInfo)
+ {
+ LastBuildInfo = buildInfo;
+ IsBuildingOngoing = true;
+ BuildResult = null;
+
+ _problemsView.Clear();
+ _outputView.Clear();
+
+ _problemsView.UpdateProblemsView();
+
+ EmitSignal(SignalName.BuildStateChanged);
+ }
+
+ private void BuildFinished(BuildResult result)
+ {
+ IsBuildingOngoing = false;
+ BuildResult = result;
+
+ string csvFile = Path.Combine(LastBuildInfo!.LogsDirPath, BuildManager.MsBuildIssuesFileName);
+ _problemsView.SetDiagnosticsFromFile(csvFile);
+
+ _problemsView.UpdateProblemsView();
+
+ EmitSignal(SignalName.BuildStateChanged);
+ }
+
+ private void UpdateBuildLogText()
+ {
+ lock (_pendingBuildLogTextLock)
+ {
+ _outputView.Append(_pendingBuildLogText);
+ _pendingBuildLogText = string.Empty;
}
}
- private enum BuildMenuOptions
+ private void StdOutputReceived(string text)
{
- BuildProject,
- RebuildProject,
- CleanProject
+ lock (_pendingBuildLogTextLock)
+ {
+ if (_pendingBuildLogText.Length == 0)
+ CallDeferred(nameof(UpdateBuildLogText));
+ _pendingBuildLogText += text + "\n";
+ }
+ }
+
+ private void StdErrorReceived(string text)
+ {
+ lock (_pendingBuildLogTextLock)
+ {
+ if (_pendingBuildLogText.Length == 0)
+ CallDeferred(nameof(UpdateBuildLogText));
+ _pendingBuildLogText += text + "\n";
+ }
}
public override void _Ready()
{
base._Ready();
- CustomMinimumSize = new Vector2(0, 228 * EditorScale);
- SizeFlagsVertical = SizeFlags.ExpandFill;
+ var bottomPanelStylebox = EditorInterface.Singleton.GetBaseControl().GetThemeStylebox("BottomPanel", "EditorStyles");
+ AddThemeConstantOverride("margin_top", -(int)bottomPanelStylebox.ContentMarginTop);
+ AddThemeConstantOverride("margin_left", -(int)bottomPanelStylebox.ContentMarginLeft);
+ AddThemeConstantOverride("margin_right", -(int)bottomPanelStylebox.ContentMarginRight);
+
+ var tabs = new TabContainer();
+ AddChild(tabs);
- var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
- AddChild(toolBarHBox);
+ var tabActions = new HBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ Alignment = BoxContainer.AlignmentMode.End,
+ };
+ tabActions.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
+ tabs.GetTabBar().AddChild(tabActions);
- _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") };
- toolBarHBox.AddChild(_buildMenuBtn);
+ _buildMenuButton = new MenuButton
+ {
+ TooltipText = "Build".TTR(),
+ Flat = true,
+ };
+ tabActions.AddChild(_buildMenuButton);
- var buildMenu = _buildMenuBtn.GetPopup();
+ var buildMenu = _buildMenuButton.GetPopup();
buildMenu.AddItem("Build Project".TTR(), (int)BuildMenuOptions.BuildProject);
buildMenu.AddItem("Rebuild Project".TTR(), (int)BuildMenuOptions.RebuildProject);
buildMenu.AddItem("Clean Project".TTR(), (int)BuildMenuOptions.CleanProject);
buildMenu.IdPressed += BuildMenuOptionPressed;
- _errorsBtn = new Button
+ _openLogsFolderButton = new Button
{
- TooltipText = "Show Errors".TTR(),
- Icon = GetThemeIcon("StatusError", "EditorIcons"),
- ExpandIcon = false,
- ToggleMode = true,
- ButtonPressed = true,
- FocusMode = FocusModeEnum.None
+ TooltipText = "Show Logs in File Manager".TTR(),
+ Flat = true,
};
- _errorsBtn.Toggled += ErrorsToggled;
- toolBarHBox.AddChild(_errorsBtn);
+ _openLogsFolderButton.Pressed += OpenLogsFolder;
+ tabActions.AddChild(_openLogsFolderButton);
- _warningsBtn = new Button
- {
- TooltipText = "Show Warnings".TTR(),
- Icon = GetThemeIcon("NodeWarning", "EditorIcons"),
- ExpandIcon = false,
- ToggleMode = true,
- ButtonPressed = true,
- FocusMode = FocusModeEnum.None
- };
- _warningsBtn.Toggled += WarningsToggled;
- toolBarHBox.AddChild(_warningsBtn);
+ _problemsView = new BuildProblemsView();
+ tabs.AddChild(_problemsView);
- _viewLogBtn = new Button
- {
- Text = "Show Output".TTR(),
- ToggleMode = true,
- ButtonPressed = true,
- FocusMode = FocusModeEnum.None
- };
- _viewLogBtn.Toggled += ViewLogToggled;
- toolBarHBox.AddChild(_viewLogBtn);
-
- // Horizontal spacer, push everything to the right.
- toolBarHBox.AddChild(new Control
- {
- SizeFlagsHorizontal = SizeFlags.ExpandFill,
- });
+ _outputView = new BuildOutputView();
+ tabs.AddChild(_outputView);
- _openLogsFolderBtn = new Button
- {
- Text = "Show Logs in File Manager".TTR(),
- Icon = GetThemeIcon("Filesystem", "EditorIcons"),
- ExpandIcon = false,
- FocusMode = FocusModeEnum.None,
- };
- _openLogsFolderBtn.Pressed += OpenLogsFolderPressed;
- toolBarHBox.AddChild(_openLogsFolderBtn);
+ UpdateTheme();
- BuildOutputView = new BuildOutputView();
- AddChild(BuildOutputView);
+ AddBuildEventListeners();
}
public override void _Notification(int what)
@@ -183,13 +256,49 @@ namespace GodotTools.Build
if (what == NotificationThemeChanged)
{
- if (_buildMenuBtn != null)
- _buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons");
- if (_errorsBtn != null)
- _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons");
- if (_warningsBtn != null)
- _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons");
+ UpdateTheme();
}
}
+
+ private void UpdateTheme()
+ {
+ // Nodes will be null until _Ready is called.
+ if (_buildMenuButton == null)
+ return;
+
+ _buildMenuButton.Icon = GetThemeIcon("BuildCSharp", "EditorIcons");
+ _openLogsFolderButton.Icon = GetThemeIcon("Filesystem", "EditorIcons");
+ }
+
+ private void AddBuildEventListeners()
+ {
+ BuildManager.BuildLaunchFailed += BuildLaunchFailed;
+ BuildManager.BuildStarted += BuildStarted;
+ BuildManager.BuildFinished += BuildFinished;
+ // StdOutput/Error can be received from different threads, so we need to use CallDeferred.
+ BuildManager.StdOutputReceived += StdOutputReceived;
+ BuildManager.StdErrorReceived += StdErrorReceived;
+ }
+
+ public void OnBeforeSerialize()
+ {
+ // In case it didn't update yet. We don't want to have to serialize any pending output.
+ UpdateBuildLogText();
+
+ // NOTE:
+ // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are.
+ // Until that changes, we need workarounds like this one because events keep strong references to disposed objects.
+ BuildManager.BuildLaunchFailed -= BuildLaunchFailed;
+ BuildManager.BuildStarted -= BuildStarted;
+ BuildManager.BuildFinished -= BuildFinished;
+ // StdOutput/Error can be received from different threads, so we need to use CallDeferred
+ BuildManager.StdOutputReceived -= StdOutputReceived;
+ BuildManager.StdErrorReceived -= StdErrorReceived;
+ }
+
+ public void OnAfterDeserialize()
+ {
+ AddBuildEventListeners(); // Re-add them.
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
index 94efcba3f1..b16adb6f55 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -592,7 +592,6 @@ MONO_AOT_MODE_LAST = 1000,
switch (platform)
{
case OS.Platforms.Windows:
- case OS.Platforms.UWP:
{
return $"windows-{arch}";
}
@@ -604,10 +603,6 @@ MONO_AOT_MODE_LAST = 1000,
{
return $"linux-{arch}";
}
- case OS.Platforms.Haiku:
- {
- return $"{platform}-{arch}";
- }
default:
throw new NotSupportedException($"Platform not supported: {platform}");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 4d61372ab0..b98df190ca 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -170,7 +170,7 @@ namespace GodotTools.Export
string ridOS = DetermineRuntimeIdentifierOS(platform);
string ridArch = DetermineRuntimeIdentifierArch(arch);
string runtimeIdentifier = $"{ridOS}-{ridArch}";
- string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{arch}";
+ string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
if (platform == OS.Platforms.MacOS)
{
projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 622a155d37..48e654c286 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -30,6 +30,7 @@ namespace GodotTools
public const string VerbosityLevel = "dotnet/build/verbosity_level";
public const string NoConsoleLogging = "dotnet/build/no_console_logging";
public const string CreateBinaryLog = "dotnet/build/create_binary_log";
+ public const string ProblemsLayout = "dotnet/build/problems_layout";
}
private EditorSettings _editorSettings;
@@ -190,6 +191,9 @@ namespace GodotTools
case ExternalEditorId.CustomEditor:
{
string file = ProjectSettings.GlobalizePath(script.ResourcePath);
+ string project = ProjectSettings.GlobalizePath("res://");
+ // Since ProjectSettings.GlobalizePath replaces only "res:/", leaving a trailing slash, it is removed here.
+ project = project[..^1];
var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>();
var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>();
var args = new List<string>();
@@ -226,6 +230,7 @@ namespace GodotTools
hasFileFlag = true;
}
+ arg = arg.ReplaceN("{project}", project);
arg = arg.ReplaceN("{file}", file);
args.Add(arg);
@@ -280,7 +285,7 @@ namespace GodotTools
case ExternalEditorId.Rider:
{
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
- RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line);
+ RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line + 1, col);
return Error.Ok;
}
case ExternalEditorId.MonoDevelop:
@@ -433,14 +438,14 @@ namespace GodotTools
private void BuildStateChanged()
{
if (_bottomPanelBtn != null)
- _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon;
+ _bottomPanelBtn.Icon = MSBuildPanel.GetBuildStateIcon();
}
public override void _EnablePlugin()
{
base._EnablePlugin();
- ProjectSettingsChanged += GodotSharpDirs.DetermineProjectLocation;
+ ProjectSettings.SettingsChanged += GodotSharpDirs.DetermineProjectLocation;
if (Instance != null)
throw new InvalidOperationException();
@@ -473,10 +478,9 @@ namespace GodotTools
}
}
- var editorInterface = GetEditorInterface();
- var editorBaseControl = editorInterface.GetBaseControl();
+ var editorBaseControl = EditorInterface.Singleton.GetBaseControl();
- _editorSettings = editorInterface.GetEditorSettings();
+ _editorSettings = EditorInterface.Singleton.GetEditorSettings();
_errorDialog = new AcceptDialog();
editorBaseControl.AddChild(_errorDialog);
@@ -486,8 +490,7 @@ namespace GodotTools
editorBaseControl.AddChild(_confirmCreateSlnDialog);
MSBuildPanel = new MSBuildPanel();
- MSBuildPanel.Ready += () =>
- MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
+ MSBuildPanel.BuildStateChanged += BuildStateChanged;
_bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR());
AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" });
@@ -497,18 +500,20 @@ namespace GodotTools
AddToolSubmenuItem("C#", _menuPopup);
- var buildSolutionShortcut = (Shortcut)EditorShortcut("mono/build_solution");
-
_toolBarBuildButton = new Button
{
- Text = "Build",
- TooltipText = "Build Solution".TTR(),
+ Flat = true,
+ Icon = EditorInterface.Singleton.GetEditorTheme().GetIcon("BuildCSharp", "EditorIcons"),
FocusMode = Control.FocusModeEnum.None,
- Shortcut = buildSolutionShortcut,
- ShortcutInTooltip = true
+ Shortcut = EditorDefShortcut("mono/build_solution", "Build Project".TTR(), (Key)KeyModifierMask.MaskAlt | Key.B),
+ ShortcutInTooltip = true,
};
+ EditorShortcutOverride("mono/build_solution", "macos", (Key)KeyModifierMask.MaskMeta | (Key)KeyModifierMask.MaskCtrl | Key.B);
+
_toolBarBuildButton.Pressed += BuildProjectPressed;
- AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton);
+ Internal.EditorPlugin_AddControlToEditorRunBar(_toolBarBuildButton);
+ // Move Build button so it appears to the left of the Play button.
+ _toolBarBuildButton.GetParent().MoveChild(_toolBarBuildButton, 0);
if (File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
@@ -530,6 +535,7 @@ namespace GodotTools
EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal));
EditorDef(Settings.NoConsoleLogging, false);
EditorDef(Settings.CreateBinaryLog, false);
+ EditorDef(Settings.ProblemsLayout, Variant.From(BuildProblemsView.ProblemsLayout.Tree));
string settingsHintStr = "Disabled";
@@ -538,7 +544,7 @@ namespace GodotTools
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsMacOS)
@@ -546,14 +552,14 @@ namespace GodotTools
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
@@ -588,6 +594,14 @@ namespace GodotTools
["hint_string"] = string.Join(",", verbosityLevels),
});
+ _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = (int)Variant.Type.Int,
+ ["name"] = Settings.ProblemsLayout,
+ ["hint"] = (int)PropertyHint.Enum,
+ ["hint_string"] = "View as List,View as Tree",
+ });
+
OnSettingsChanged();
_editorSettings.SettingsChanged += OnSettingsChanged;
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 4a0b7f9bed..56ae37b4dd 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.1" />
+ <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.4" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<Reference Include="GodotSharp">
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 5d1a2277f9..65b77112aa 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -79,7 +79,7 @@ namespace GodotTools.Ides
public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
{
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
var editorId = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>();
string editorIdentity = GetExternalEditorIdentity(editorId);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
index 7e08d8c01d..61c1581281 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
@@ -48,4 +48,9 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
else
GD.PushError(message, e);
}
+
+ public void Verbose(string message, Exception e = null)
+ {
+ // do nothing, since IDK how to write only to the log, without spamming the output
+ }
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
index 5c09f1f83a..a0ab381b9b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
@@ -1,41 +1,48 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using Godot;
using GodotTools.Internals;
using JetBrains.Rider.PathLocator;
+#nullable enable
+
namespace GodotTools.Ides.Rider
{
public static class RiderPathManager
{
+ private const string EditorPathSettingName = "dotnet/editor/editor_path_optional";
+
private static readonly RiderPathLocator RiderPathLocator;
+ private static readonly RiderFileOpener RiderFileOpener;
static RiderPathManager()
{
- RiderPathLocator = new RiderPathLocator(new RiderLocatorEnvironment());
+ var riderLocatorEnvironment = new RiderLocatorEnvironment();
+ RiderPathLocator = new RiderPathLocator(riderLocatorEnvironment);
+ RiderFileOpener = new RiderFileOpener(riderLocatorEnvironment);
}
- public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional";
-
- private static string GetRiderPathFromSettings()
+ private static string? GetRiderPathFromSettings()
{
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
if (editorSettings.HasSetting(EditorPathSettingName))
+ {
return (string)editorSettings.GetSetting(EditorPathSettingName);
+ }
+
return null;
}
public static void Initialize()
{
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
var editor = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>();
if (editor == ExternalEditorId.Rider)
{
if (!editorSettings.HasSetting(EditorPathSettingName))
{
- Globals.EditorDef(EditorPathSettingName, "Optional");
+ Globals.EditorDef(EditorPathSettingName, "");
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = (int)Variant.Type.String,
@@ -46,16 +53,17 @@ namespace GodotTools.Ides.Rider
}
var riderPath = (string)editorSettings.GetSetting(EditorPathSettingName);
- if (IsRiderAndExists(riderPath))
+ if (File.Exists(riderPath))
{
Globals.EditorDef(EditorPathSettingName, riderPath);
return;
}
var paths = RiderPathLocator.GetAllRiderPaths();
-
- if (!paths.Any())
+ if (paths.Length == 0)
+ {
return;
+ }
string newPath = paths.Last().Path;
Globals.EditorDef(EditorPathSettingName, newPath);
@@ -63,70 +71,51 @@ namespace GodotTools.Ides.Rider
}
}
- public static bool IsExternalEditorSetToRider(EditorSettings editorSettings)
- {
- return editorSettings.HasSetting(EditorPathSettingName) &&
- IsRider((string)editorSettings.GetSetting(EditorPathSettingName));
- }
-
public static bool IsRider(string path)
{
- if (string.IsNullOrEmpty(path))
- return false;
-
if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1)
+ {
return false;
+ }
var fileInfo = new FileInfo(path);
- string filename = fileInfo.Name.ToLowerInvariant();
- return filename.StartsWith("rider", StringComparison.Ordinal);
+ return fileInfo.Name.StartsWith("rider", StringComparison.OrdinalIgnoreCase);
}
- private static string CheckAndUpdatePath(string riderPath)
+ private static string? CheckAndUpdatePath(string? riderPath)
{
- if (IsRiderAndExists(riderPath))
+ if (File.Exists(riderPath))
{
return riderPath;
}
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var paths = RiderPathLocator.GetAllRiderPaths();
-
- if (!paths.Any())
+ var allInfos = RiderPathLocator.GetAllRiderPaths();
+ if (allInfos.Length == 0)
+ {
return null;
+ }
- string newPath = paths.Last().Path;
+ // RiderPathLocator includes Rider and Fleet locations, prefer Rider when available.
+ var preferredInfo = allInfos.LastOrDefault(info => IsRider(info.Path), allInfos[allInfos.Length - 1]);
+ string newPath = preferredInfo.Path;
+
+ var editorSettings = EditorInterface.Singleton.GetEditorSettings();
editorSettings.SetSetting(EditorPathSettingName, newPath);
Globals.EditorDef(EditorPathSettingName, newPath);
return newPath;
}
- private static bool IsRiderAndExists(string riderPath)
- {
- return !string.IsNullOrEmpty(riderPath) && IsRider(riderPath) && new FileInfo(riderPath).Exists;
- }
-
- public static void OpenFile(string slnPath, string scriptPath, int line)
+ public static void OpenFile(string slnPath, string scriptPath, int line, int column)
{
- string pathFromSettings = GetRiderPathFromSettings();
- string path = CheckAndUpdatePath(pathFromSettings);
-
- var args = new List<string>();
- args.Add(slnPath);
- if (line >= 0)
- {
- args.Add("--line");
- args.Add((line + 1).ToString()); // https://github.com/JetBrains/godot-support/issues/61
- }
- args.Add(scriptPath);
- try
- {
- Utils.OS.RunProcess(path, args);
- }
- catch (Exception e)
+ string? pathFromSettings = GetRiderPathFromSettings();
+ string? path = CheckAndUpdatePath(pathFromSettings);
+ if (string.IsNullOrEmpty(path))
{
- GD.PushError($"Error when trying to run code editor: JetBrains Rider. Exception message: '{e.Message}'");
+ GD.PushError($"Error when trying to run code editor: JetBrains Rider or Fleet. Could not find path to the editor.");
+ return;
}
+
+ RiderFileOpener.OpenFile(path, slnPath, scriptPath, line, column);
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
index 45ae7eb86b..a6718e8fd5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
@@ -29,11 +29,26 @@ namespace GodotTools.Internals
return Variant.CreateTakingOwnershipOfDisposableValue(result);
}
- public static Variant EditorShortcut(string setting)
+ public static Shortcut EditorDefShortcut(string setting, string name, Key keycode = Key.None, bool physical = false)
{
using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
- Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result);
- return Variant.CreateTakingOwnershipOfDisposableValue(result);
+ using godot_string nameIn = Marshaling.ConvertStringToNative(name);
+ Internal.godot_icall_Globals_EditorDefShortcut(settingIn, nameIn, keycode, physical.ToGodotBool(), out godot_variant result);
+ return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result);
+ }
+
+ public static Shortcut EditorGetShortcut(string setting)
+ {
+ using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
+ Internal.godot_icall_Globals_EditorGetShortcut(settingIn, out godot_variant result);
+ return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result);
+ }
+
+ public static void EditorShortcutOverride(string setting, string feature, Key keycode = Key.None, bool physical = false)
+ {
+ using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
+ using godot_string featureIn = Marshaling.ConvertStringToNative(feature);
+ Internal.godot_icall_Globals_EditorShortcutOverride(settingIn, featureIn, keycode, physical.ToGodotBool());
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 3ea11750b7..90c443ebb8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -54,6 +54,9 @@ namespace GodotTools.Internals
public static void EditorRunStop() => godot_icall_Internal_EditorRunStop();
+ public static void EditorPlugin_AddControlToEditorRunBar(Control control) =>
+ godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(control.NativeInstance);
+
public static void ScriptEditorDebugger_ReloadScripts() =>
godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
@@ -137,6 +140,8 @@ namespace GodotTools.Internals
private static partial void godot_icall_Internal_EditorRunStop();
+ private static partial void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(IntPtr p_control);
+
private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile,
@@ -151,7 +156,13 @@ namespace GodotTools.Internals
bool restartIfChanged, out godot_variant result);
public static partial void
- godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result);
+ godot_icall_Globals_EditorDefShortcut(in godot_string setting, in godot_string name, Key keycode, godot_bool physical, out godot_variant result);
+
+ public static partial void
+ godot_icall_Globals_EditorGetShortcut(in godot_string setting, out godot_variant result);
+
+ public static partial void
+ godot_icall_Globals_EditorShortcutOverride(in godot_string setting, in godot_string feature, Key keycode, godot_bool physical);
public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index c16f803226..bff0c0df7c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -26,8 +26,6 @@ namespace GodotTools.Utils
public const string FreeBSD = "FreeBSD";
public const string NetBSD = "NetBSD";
public const string BSD = "BSD";
- public const string UWP = "UWP";
- public const string Haiku = "Haiku";
public const string Android = "Android";
public const string iOS = "iOS";
public const string Web = "Web";
@@ -41,8 +39,6 @@ namespace GodotTools.Utils
public const string Windows = "windows";
public const string MacOS = "macos";
public const string LinuxBSD = "linuxbsd";
- public const string UWP = "uwp";
- public const string Haiku = "haiku";
public const string Android = "android";
public const string iOS = "ios";
public const string Web = "web";
@@ -71,8 +67,6 @@ namespace GodotTools.Utils
["Windows"] = Platforms.Windows,
["macOS"] = Platforms.MacOS,
["Linux"] = Platforms.LinuxBSD,
- ["UWP"] = Platforms.UWP,
- ["Haiku"] = Platforms.Haiku,
["Android"] = Platforms.Android,
["iOS"] = Platforms.iOS,
["Web"] = Platforms.Web
@@ -86,8 +80,6 @@ namespace GodotTools.Utils
[Names.FreeBSD] = Platforms.LinuxBSD,
[Names.NetBSD] = Platforms.LinuxBSD,
[Names.BSD] = Platforms.LinuxBSD,
- [Names.UWP] = Platforms.UWP,
- [Names.Haiku] = Platforms.Haiku,
[Names.Android] = Platforms.Android,
[Names.iOS] = Platforms.iOS,
[Names.Web] = Platforms.Web
@@ -102,7 +94,6 @@ namespace GodotTools.Utils
// instead of `linux` in the runtime identifier. This would be a problem as
// Godot has a single export profile for both, named LinuxBSD.
[Platforms.LinuxBSD] = DotNetOS.Linux,
- [Platforms.UWP] = DotNetOS.Win10,
[Platforms.Android] = DotNetOS.Android,
[Platforms.iOS] = DotNetOS.iOS,
[Platforms.Web] = DotNetOS.Browser
@@ -132,29 +123,23 @@ namespace GodotTools.Utils
new[] { Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD };
private static readonly IEnumerable<string> UnixLikePlatforms =
- new[] { Names.MacOS, Names.Haiku, Names.Android, Names.iOS }
+ new[] { Names.MacOS, Names.Android, Names.iOS }
.Concat(LinuxBSDPlatforms).ToArray();
private static readonly Lazy<bool> _isWindows = new(() => IsOS(Names.Windows));
private static readonly Lazy<bool> _isMacOS = new(() => IsOS(Names.MacOS));
private static readonly Lazy<bool> _isLinuxBSD = new(() => IsAnyOS(LinuxBSDPlatforms));
- private static readonly Lazy<bool> _isUWP = new(() => IsOS(Names.UWP));
- private static readonly Lazy<bool> _isHaiku = new(() => IsOS(Names.Haiku));
private static readonly Lazy<bool> _isAndroid = new(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isiOS = new(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isWeb = new(() => IsOS(Names.Web));
private static readonly Lazy<bool> _isUnixLike = new(() => IsAnyOS(UnixLikePlatforms));
- [SupportedOSPlatformGuard("windows")] public static bool IsWindows => _isWindows.Value || IsUWP;
+ [SupportedOSPlatformGuard("windows")] public static bool IsWindows => _isWindows.Value;
[SupportedOSPlatformGuard("osx")] public static bool IsMacOS => _isMacOS.Value;
[SupportedOSPlatformGuard("linux")] public static bool IsLinuxBSD => _isLinuxBSD.Value;
- [SupportedOSPlatformGuard("windows")] public static bool IsUWP => _isUWP.Value;
-
- public static bool IsHaiku => _isHaiku.Value;
-
[SupportedOSPlatformGuard("android")] public static bool IsAndroid => _isAndroid.Value;
[SupportedOSPlatformGuard("ios")] public static bool IsiOS => _isiOS.Value;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index cff41a57f3..ccfcf2a87c 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -82,6 +82,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define CS_STATIC_METHOD_GETINSTANCE "GetPtr"
#define CS_METHOD_CALL "Call"
#define CS_PROPERTY_SINGLETON "Singleton"
+#define CS_SINGLETON_INSTANCE_SUFFIX "Instance"
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"
#define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal"
@@ -93,6 +94,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define ICALL_PREFIX "godot_icall_"
#define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method"
+#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility"
#define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor"
#define C_LOCAL_RET "ret"
@@ -116,7 +118,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
// Types that will be ignored by the generator and won't be available in C#.
// This must be kept in sync with `ignored_types` in csharp_script.cpp
-const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" };
+const Vector<String> ignored_types = {};
void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) {
// C interface for enums is the same as that of 'uint32_t'. Remember to apply
@@ -147,7 +149,7 @@ static String fix_doc_description(const String &p_bbcode) {
.strip_edges();
}
-String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) {
+String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {
// Based on the version in EditorHelp
if (p_bbcode.is_empty()) {
@@ -304,11 +306,11 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts);
} else if (link_tag == "constant") {
_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);
+ } else if (link_tag == "param") {
+ _append_xml_param(xml_output, link_target, p_is_signal);
} else if (link_tag == "theme_item") {
// We do not declare theme_items in any way in C#, so there is nothing to reference
_append_xml_undeclared(xml_output, link_target);
- } else if (link_tag == "param") {
- _append_xml_undeclared(xml_output, snake_to_camel_case(link_target, false));
}
pos = brk_end + 1;
@@ -366,9 +368,19 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
}
if (target_itype) {
- xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
- xml_output.append(target_itype->proxy_name);
- xml_output.append("\"/>");
+ if ((!p_itype || p_itype->api_type == ClassDB::API_CORE) && target_itype->api_type == ClassDB::API_EDITOR) {
+ // Editor references in core documentation cannot be resolved,
+ // handle as standard codeblock.
+ _log("Cannot reference editor type '%s' in documentation for core type '%s'\n",
+ target_itype->proxy_name.utf8().get_data(), p_itype ? p_itype->proxy_name.utf8().get_data() : "@GlobalScope");
+ xml_output.append("<c>");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append("</c>");
+ } else {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append("\"/>");
+ }
} else {
ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");
@@ -522,7 +534,31 @@ void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const Ty
p_xml_output.append(p_target_itype->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_imethod->proxy_name);
- p_xml_output.append("\"/>");
+ p_xml_output.append("(");
+ bool first_key = true;
+ for (const ArgumentInterface &iarg : target_imethod->arguments) {
+ const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+
+ if (first_key) {
+ first_key = false;
+ } else {
+ p_xml_output.append(", ");
+ }
+ if (!arg_type) {
+ ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");
+ p_xml_output.append(iarg.type.cname);
+ continue;
+ }
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
+ p_xml_output.append("Nullable{");
+ }
+ String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
+ p_xml_output.append(arg_cs_type.replacen("<", "{").replacen(">", "}").replacen("params ", ""));
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
+ p_xml_output.append("}");
+ }
+ }
+ p_xml_output.append(")\"/>");
} else {
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");
@@ -653,6 +689,11 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const
_append_xml_undeclared(p_xml_output, p_link_target);
} else {
// Try to find the constant in the current class
+ if (p_target_itype->is_singleton_instance) {
+ // Constants and enums are declared in the static singleton class.
+ p_target_itype = &obj_types[p_target_itype->cname];
+ }
+
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);
if (target_iconst) {
@@ -678,7 +719,7 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
p_xml_output.append(p_target_itype->proxy_name);
p_xml_output.append(".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -719,7 +760,7 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
if (target_iconst) {
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -730,6 +771,21 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
}
}
+void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {
+ const String link_target = snake_to_camel_case(p_link_target);
+
+ if (!p_is_signal) {
+ p_xml_output.append("<paramref name=\"");
+ p_xml_output.append(link_target);
+ p_xml_output.append("\"/>");
+ } else {
+ // Documentation in C# is added to an event, not the delegate itself;
+ // as such, we treat these parameters as codeblocks instead.
+ // See: https://github.com/godotengine/godot/pull/65529
+ _append_xml_undeclared(p_xml_output, link_target);
+ }
+}
+
void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {
p_xml_output.append("<c>");
p_xml_output.append(p_link_target);
@@ -934,9 +990,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");
- p_output.append("\n#pragma warning disable CS1591 // Disable warning: "
- "'Missing XML comment for publicly visible type or member'\n");
-
p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{");
for (const ConstantInterface &iconstant : global_constants) {
@@ -975,7 +1028,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
for (const EnumInterface &ienum : global_enums) {
CRASH_COND(ienum.constants.is_empty());
- String enum_proxy_name = ienum.cname.operator String();
+ String enum_proxy_name = ienum.proxy_name;
bool enum_in_static_class = false;
@@ -989,7 +1042,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());
p_output.append("\npublic partial struct ");
- p_output.append(pascal_to_pascal_case(enum_class_name));
+ p_output.append(enum_class_name);
p_output.append("\n" OPEN_BLOCK);
}
@@ -998,7 +1051,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
}
p_output.append("\npublic enum ");
- p_output.append(pascal_to_pascal_case(enum_proxy_name));
+ p_output.append(enum_proxy_name);
p_output.append(" : long");
p_output.append("\n" OPEN_BLOCK);
@@ -1034,8 +1087,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append(CLOSE_BLOCK);
}
}
-
- p_output.append("\n#pragma warning restore CS1591\n");
}
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
@@ -1348,15 +1399,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append("namespace " BINDINGS_NAMESPACE ";\n\n");
output.append("using System;\n"); // IntPtr
+ output.append("using System.ComponentModel;\n"); // EditorBrowsable
output.append("using System.Diagnostics;\n"); // DebuggerBrowsable
output.append("using Godot.NativeInterop;\n");
- output.append("\n"
- "#pragma warning disable CS1591 // Disable warning: "
- "'Missing XML comment for publicly visible type or member'\n"
- "#pragma warning disable CS1573 // Disable warning: "
- "'Parameter has no matching param tag in the XML comment'\n");
-
output.append("\n#nullable disable\n");
const DocData::ClassDoc *class_doc = itype.class_doc;
@@ -1403,8 +1449,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
if (is_derived_type && !itype.is_singleton) {
if (obj_types.has(itype.base_name)) {
+ TypeInterface base_type = obj_types[itype.base_name];
output.append(" : ");
- output.append(obj_types[itype.base_name].proxy_name);
+ output.append(base_type.proxy_name);
+ if (base_type.is_singleton) {
+ // If the type is a singleton, use the instance type.
+ output.append(CS_SINGLETON_INSTANCE_SUFFIX);
+ }
} else {
ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");
return ERR_INVALID_DATA;
@@ -1458,7 +1509,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output.append(MEMBER_BEGIN "public enum ");
- output.append(pascal_to_pascal_case(ienum.cname.operator String()));
+ output.append(ienum.proxy_name);
output.append(" : long");
output.append(MEMBER_BEGIN OPEN_BLOCK);
@@ -1504,37 +1555,44 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
"' for class '" + itype.name + "'.");
}
- if (itype.is_singleton) {
- // Add the type name and the singleton pointer as static fields
+ // Add native name static field and cached type.
+
+ if (is_derived_type && !itype.is_singleton) {
+ output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
+ }
- output.append(MEMBER_BEGIN "private static GodotObject singleton;\n");
+ output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(itype.name);
+ output.append("\";\n");
- output << MEMBER_BEGIN "public static GodotObject " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n"
- << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n"
- << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(\""
- << itype.name
- << "\");\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n";
+ if (itype.is_singleton || itype.is_compat_singleton) {
+ // Add the Singleton static property.
- output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.append(itype.name);
- output.append("\";\n");
- } else {
+ String instance_type_name;
+
+ if (itype.is_singleton) {
+ StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX;
+ instance_type_name = obj_types.has(instance_name)
+ ? obj_types[instance_name].proxy_name
+ : "GodotObject";
+ } else {
+ instance_type_name = itype.proxy_name;
+ }
+
+ output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n");
+
+ output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n"
+ << INDENT2 "singleton \?\?= (" + instance_type_name + ")"
+ << C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n";
+ }
+
+ if (!itype.is_singleton) {
// IMPORTANT: We also generate the static fields for GodotObject instead of declaring
// them manually in the `GodotObject.base.cs` partial class declaration, because they're
// required by other static fields in this generated partial class declaration.
// Static fields are initialized in order of declaration, but when they're in different
// partial class declarations then it becomes harder to tell (Rider warns about this).
- // Add native name static field
-
- if (is_derived_type) {
- output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
- }
-
- output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.append(itype.name);
- output.append("\";\n");
-
if (itype.is_instantiable) {
// Add native constructor static field
@@ -1749,7 +1807,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
<< INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n"
<< INDENT1 "/// Do not call or override this method.\n"
<< INDENT1 "/// </summary>\n"
- << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n";
+ << INDENT1 "/// <param name=\"signal\">Name of the signal to check for.</param>\n";
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
<< " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n"
@@ -1809,7 +1867,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output << "\n"
<< INDENT1 "{\n";
+ HashMap<String, StringName> method_names;
for (const MethodInterface &imethod : itype.methods) {
+ if (method_names.has(imethod.proxy_name)) {
+ ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value.");
+ continue;
+ }
+ method_names[imethod.proxy_name] = imethod.cname;
output << INDENT2 "/// <summary>\n"
<< INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n"
<< INDENT2 "/// </summary>\n"
@@ -1837,10 +1901,6 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(CLOSE_BLOCK /* class */);
- output.append("\n"
- "#pragma warning restore CS1591\n"
- "#pragma warning restore CS1573\n");
-
return _save_file(p_output_file, output);
}
@@ -1894,7 +1954,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;
- const TypeInterface *prop_itype = _get_type_or_null(proptype_name);
+ const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name);
ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found
ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,
@@ -1983,7 +2043,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
}
Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
- const TypeInterface *return_type = _get_type_or_null(p_imethod.return_type);
+ const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found
ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,
@@ -2004,12 +2064,17 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String icall_params = method_bind_field;
if (!p_imethod.is_static) {
+ String self_reference = "this";
+ if (p_itype.is_singleton) {
+ self_reference = CS_PROPERTY_SINGLETON;
+ }
+
if (p_itype.cs_in.size()) {
- cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, "this",
+ cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference,
String(), String(), String(), INDENT2);
}
- icall_params += ", " + sformat(p_itype.cs_in_expr, "this");
+ icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference);
}
StringBuilder default_args_doc;
@@ -2017,7 +2082,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Retrieve information from the arguments
const ArgumentInterface &first = p_imethod.arguments.front()->get();
for (const ArgumentInterface &iarg : p_imethod.arguments) {
- const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
@@ -2057,7 +2122,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += iarg.name;
- if (iarg.default_argument.size()) {
+ if (!p_imethod.is_compat && iarg.default_argument.size()) {
if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
arguments_sig += " = null";
} else {
@@ -2128,6 +2193,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}
+ // Collect caller name for MethodBind
+ if (p_imethod.is_vararg) {
+ icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
+ }
+
// Generate method
{
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
@@ -2140,8 +2210,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
p_output << "GodotObject.";
}
- p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName."
- << p_imethod.proxy_name
+ p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName."
+ << p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul"
<< ");\n";
}
@@ -2180,6 +2250,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
p_output.append("\")]");
}
+ if (p_imethod.is_compat) {
+ p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");
+ }
+
p_output.append(MEMBER_BEGIN);
p_output.append(p_imethod.is_internal ? "internal " : "public ");
@@ -2270,7 +2344,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Retrieve information from the arguments
const ArgumentInterface &first = p_isignal.arguments.front()->get();
for (const ArgumentInterface &iarg : p_isignal.arguments) {
- const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
@@ -2302,31 +2376,31 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Generate signal
{
- p_output.append(MEMBER_BEGIN "/// <summary>\n");
- p_output.append(INDENT1 "/// ");
- p_output.append("Represents the method that handles the ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
- p_output.append(" event of a ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
- p_output.append(" class.\n");
- p_output.append(INDENT1 "/// </summary>");
-
- if (p_isignal.is_deprecated) {
- if (p_isignal.deprecation_message.is_empty()) {
- WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
- }
-
- p_output.append(MEMBER_BEGIN "[Obsolete(\"");
- p_output.append(p_isignal.deprecation_message);
- p_output.append("\")]");
- }
-
bool is_parameterless = p_isignal.arguments.size() == 0;
// Delegate name is [SignalName]EventHandler
String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
if (!is_parameterless) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
+ p_output.append(INDENT1 "/// ");
+ p_output.append("Represents the method that handles the ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
+ p_output.append(" event of a ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
+ p_output.append(" class.\n");
+ p_output.append(INDENT1 "/// </summary>");
+
+ if (p_isignal.is_deprecated) {
+ if (p_isignal.deprecation_message.is_empty()) {
+ WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
+ }
+
+ p_output.append(MEMBER_BEGIN "[Obsolete(\"");
+ p_output.append(p_isignal.deprecation_message);
+ p_output.append("\")]");
+ }
+
// Generate delegate
p_output.append(MEMBER_BEGIN "public delegate void ");
p_output.append(delegate_name);
@@ -2362,7 +2436,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
}
if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype);
+ String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
@@ -2501,6 +2575,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
i++;
}
+ // Collect caller name for MethodBind
+ if (p_icall.is_vararg) {
+ c_func_sig << ", godot_string_name caller";
+ }
+
String icall_method = p_icall.name;
// Generate icall function
@@ -2566,7 +2645,12 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("
<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
- << ", total_length, out _);\n";
+ << ", total_length, out godot_variant_call_error vcall_error);\n";
+
+ r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"
+ << ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
+ << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
+ << ", total_length, vcall_error);\n";
if (!ret_void) {
if (return_type->cname != name_cache.type_Variant) {
@@ -2687,6 +2771,20 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con
return nullptr;
}
+const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) {
+ const TypeInterface *itype = _get_type_or_null(p_typeref);
+ if (itype == nullptr) {
+ return nullptr;
+ }
+
+ if (itype->is_singleton) {
+ StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX;
+ itype = &obj_types.find(instance_type_name)->value;
+ }
+
+ return itype;
+}
+
const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) {
if (p_generic_type_parameters.is_empty()) {
return "";
@@ -2700,8 +2798,8 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface
int i = 0;
String params = "<";
for (const TypeReference &param_type : p_generic_type_parameters) {
- const TypeInterface *param_itype = _get_type_or_null(param_type);
- ERR_FAIL_NULL_V(param_itype, "");
+ const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type);
+ ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found
ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "",
"Generic type parameter is a singleton: '" + param_itype->name + "'.");
@@ -2872,6 +2970,12 @@ bool method_has_ptr_parameter(MethodInfo p_method_info) {
return false;
}
+struct SortMethodWithHashes {
+ _FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const {
+ return p_a.first < p_b.first;
+ }
+};
+
bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
@@ -2917,17 +3021,18 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);
itype.memory_own = itype.is_ref_counted;
+ if (itype.is_singleton && compat_singletons.has(itype.cname)) {
+ itype.is_singleton = false;
+ itype.is_compat_singleton = true;
+ }
+
itype.c_out = "%5return ";
itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;
itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";
itype.cs_type = itype.proxy_name;
- if (itype.is_singleton) {
- itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(" CS_PROPERTY_SINGLETON ")";
- } else {
- itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";
- }
+ itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";
itype.cs_out = "%5return (%2)%0(%1);";
@@ -2998,11 +3103,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
List<MethodInfo> virtual_method_list;
ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true);
- List<MethodInfo> method_list;
- ClassDB::get_method_list(type_cname, &method_list, true);
- method_list.sort();
+ List<Pair<MethodInfo, uint32_t>> method_list_with_hashes;
+ ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true);
+ method_list_with_hashes.sort_custom_inplace<SortMethodWithHashes>();
+
+ List<MethodInterface> compat_methods;
+ for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) {
+ const MethodInfo &method_info = E.first;
+ const uint32_t hash = E.second;
- for (const MethodInfo &method_info : method_list) {
int argc = method_info.arguments.size();
if (method_info.name.is_empty()) {
@@ -3024,6 +3133,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
MethodInterface imethod;
imethod.name = method_info.name;
imethod.cname = cname;
+ imethod.hash = hash;
if (method_info.flags & METHOD_FLAG_STATIC) {
imethod.is_static = true;
@@ -3036,7 +3146,17 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
PropertyInfo return_info = method_info.return_val;
- MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name);
+ MethodBind *m = nullptr;
+
+ if (!imethod.is_virtual) {
+ bool method_exists = false;
+ m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat);
+
+ if (unlikely(!method_exists)) {
+ ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
+ "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
+ }
+ }
imethod.is_vararg = m && m->is_vararg();
@@ -3157,6 +3277,14 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,
"Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");
+ // Compat methods aren't added to the type yet, they need to be checked for conflicts
+ // after all the non-compat methods have been added. The compat methods are added in
+ // reverse so the most recently added ones take precedence over older compat methods.
+ if (imethod.is_compat) {
+ compat_methods.push_front(imethod);
+ continue;
+ }
+
// Methods starting with an underscore are ignored unless they're used as a property setter or getter
if (!imethod.is_virtual && imethod.name[0] == '_') {
for (const PropertyInterface &iprop : itype.properties) {
@@ -3171,6 +3299,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
}
}
+ // Add compat methods that don't conflict with other methods in the type.
+ for (const MethodInterface &imethod : compat_methods) {
+ if (_method_has_conflicting_signature(imethod, itype)) {
+ WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored.");
+ continue;
+ }
+ itype.methods.push_back(imethod);
+ }
+
// Populate signals
const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;
@@ -3263,8 +3400,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
enum_proxy_name += "Enum";
enum_proxy_cname = StringName(enum_proxy_name);
}
- EnumInterface ienum(enum_proxy_cname);
- ienum.is_flags = E.value.is_bitfield;
+ EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);
const List<StringName> &enum_constants = E.value.constants;
for (const StringName &constant_cname : enum_constants) {
String constant_name = constant_cname.operator String();
@@ -3331,6 +3467,19 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.insert(itype.cname, itype);
+ if (itype.is_singleton) {
+ // Add singleton instance type.
+ itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;
+ itype.is_singleton = false;
+ itype.is_singleton_instance = true;
+
+ // Remove constants and enums, those will remain in the static class.
+ itype.constants.clear();
+ itype.enums.clear();
+
+ obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);
+ }
+
class_list.pop_front();
}
@@ -3806,7 +3955,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
builtin_types.insert(itype.cname, itype);
// Array_@generic
- // Re-use Array's itype
+ // Reuse Array's itype
itype.name = "Array_@generic";
itype.cname = itype.name;
itype.cs_out = "%5return new %2(%0(%1));";
@@ -3833,7 +3982,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
builtin_types.insert(itype.cname, itype);
// Dictionary_@generic
- // Re-use Dictionary's itype
+ // Reuse Dictionary's itype
itype.name = "Dictionary_@generic";
itype.cname = itype.name;
itype.cs_out = "%5return new %2(%0(%1));";
@@ -3884,8 +4033,7 @@ void BindingsGenerator::_populate_global_constants() {
iconstant.const_doc = const_doc;
if (enum_name != StringName()) {
- EnumInterface ienum(enum_name);
- ienum.is_flags = CoreConstants::is_global_constant_bitfield(i);
+ EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));
List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
if (enum_match) {
enum_match->get().constants.push_back(iconstant);
@@ -3903,7 +4051,7 @@ void BindingsGenerator::_populate_global_constants() {
enum_itype.is_enum = true;
enum_itype.name = ienum.cname.operator String();
enum_itype.cname = ienum.cname;
- enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);
+ enum_itype.proxy_name = ienum.proxy_name;
TypeInterface::postsetup_enum_type(enum_itype);
enum_types.insert(enum_itype.cname, enum_itype);
@@ -3941,12 +4089,60 @@ void BindingsGenerator::_populate_global_constants() {
}
}
+bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) {
+ // Compare p_imethod with all the methods already registered in p_itype.
+ for (const MethodInterface &method : p_itype.methods) {
+ if (method.proxy_name == p_imethod.proxy_name) {
+ if (_method_has_conflicting_signature(p_imethod, method)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) {
+ // Check if a method already exists in p_itype with a method signature that would conflict with p_imethod.
+ // The return type is ignored because only changing the return type is not enough to avoid conflicts.
+ // The const keyword is also ignored since it doesn't generate different C# code.
+
+ if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) {
+ // Different argument count, so no conflict.
+ return false;
+ }
+
+ for (int i = 0; i < p_imethod_left.arguments.size(); i++) {
+ const ArgumentInterface &iarg_left = p_imethod_left.arguments[i];
+ const ArgumentInterface &iarg_right = p_imethod_right.arguments[i];
+
+ if (iarg_left.type.cname != iarg_right.type.cname) {
+ // Different types for arguments in the same position, so no conflict.
+ return false;
+ }
+
+ if (iarg_left.def_param_mode != iarg_right.def_param_mode) {
+ // If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T'
+ // and will not create a conflict.
+ if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
void BindingsGenerator::_initialize_blacklisted_methods() {
blacklisted_methods["Object"].push_back("to_string"); // there is already ToString
blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead
blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)
}
+void BindingsGenerator::_initialize_compat_singletons() {
+ compat_singletons.insert("EditorInterface");
+}
+
void BindingsGenerator::_log(const char *p_format, ...) {
if (log_print_enabled) {
va_list list;
@@ -3966,6 +4162,8 @@ void BindingsGenerator::_initialize() {
_initialize_blacklisted_methods();
+ _initialize_compat_singletons();
+
bool obj_type_ok = _populate_object_type_interfaces();
ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 38347a5181..aa4e5ea093 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -60,6 +60,7 @@ class BindingsGenerator {
struct EnumInterface {
StringName cname;
+ String proxy_name;
List<ConstantInterface> constants;
bool is_flags = false;
@@ -69,8 +70,10 @@ class BindingsGenerator {
EnumInterface() {}
- EnumInterface(const StringName &p_cname) {
+ EnumInterface(const StringName &p_cname, const String &p_proxy_name, bool p_is_flags) {
cname = p_cname;
+ proxy_name = p_proxy_name;
+ is_flags = p_is_flags;
}
};
@@ -131,6 +134,11 @@ class BindingsGenerator {
String proxy_name;
/**
+ * Hash of the ClassDB method
+ */
+ uint64_t hash = 0;
+
+ /**
* [TypeInterface::name] of the return type
*/
TypeReference return_type;
@@ -165,6 +173,12 @@ class BindingsGenerator {
*/
bool is_internal = false;
+ /**
+ * Determines if the method is a compatibility method added to avoid breaking binary compatibility.
+ * These methods will be generated but hidden and are considered deprecated.
+ */
+ bool is_compat = false;
+
List<ArgumentInterface> arguments;
const DocData::MethodDoc *method_doc = nullptr;
@@ -227,9 +241,18 @@ class BindingsGenerator {
bool is_enum = false;
bool is_object_type = false;
bool is_singleton = false;
+ bool is_singleton_instance = false;
bool is_ref_counted = false;
/**
+ * Class is a singleton, but can't be declared as a static class as that would
+ * break backwards compatibility. As such, instead of going with a static class,
+ * we use the actual singleton pattern (private constructor with instance property),
+ * which doesn't break compatibility.
+ */
+ bool is_compat_singleton = false;
+
+ /**
* Determines whether the native return value of this type must be disposed
* by the generated internal call (think of `godot_string`, whose destructor
* must be called). Some structs that are disposable may still disable this
@@ -614,8 +637,10 @@ class BindingsGenerator {
HashMap<const MethodInterface *, const InternalCall *> method_icalls_map;
HashMap<StringName, List<StringName>> blacklisted_methods;
+ HashSet<StringName> compat_singletons;
void _initialize_blacklisted_methods();
+ void _initialize_compat_singletons();
struct NameCache {
StringName type_void = StaticCString::create("void");
@@ -740,7 +765,7 @@ class BindingsGenerator {
return p_type->name;
}
- String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype);
+ String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal = false);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
@@ -748,6 +773,7 @@ class BindingsGenerator {
void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target);
+ void _append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal);
void _append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target);
int _determine_enum_prefix(const EnumInterface &p_ienum);
@@ -756,6 +782,7 @@ class BindingsGenerator {
Error _populate_method_icalls_table(const TypeInterface &p_itype);
const TypeInterface *_get_type_or_null(const TypeReference &p_typeref);
+ const TypeInterface *_get_type_or_singleton_or_null(const TypeReference &p_typeref);
const String _get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters);
@@ -771,6 +798,9 @@ class BindingsGenerator {
void _populate_global_constants();
+ bool _method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype);
+ bool _method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right);
+
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index ae02e16256..ae914e71ef 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -31,6 +31,7 @@
#include "code_completion.h"
#include "core/config/project_settings.h"
+#include "core/object/script_language.h"
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
#include "scene/gui/control.h"
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index ba6b91b704..fc99f3ceda 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -168,6 +168,10 @@ void godot_icall_Internal_EditorRunStop() {
EditorRunBar::get_singleton()->stop_playing();
}
+void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_control) {
+ EditorRunBar::get_singleton()->get_buttons_container()->add_child(p_control);
+}
+
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
if (ed) {
@@ -199,12 +203,25 @@ void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_va
memnew_placement(r_result, Variant(result));
}
-void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) {
+void godot_icall_Globals_EditorDefShortcut(const godot_string *p_setting, const godot_string *p_name, Key p_keycode, bool p_physical, godot_variant *r_result) {
+ String setting = *reinterpret_cast<const String *>(p_setting);
+ String name = *reinterpret_cast<const String *>(p_name);
+ Ref<Shortcut> result = ED_SHORTCUT(setting, name, p_keycode, p_physical);
+ memnew_placement(r_result, Variant(result));
+}
+
+void godot_icall_Globals_EditorGetShortcut(const godot_string *p_setting, Ref<Shortcut> *r_result) {
String setting = *reinterpret_cast<const String *>(p_setting);
Ref<Shortcut> result = ED_GET_SHORTCUT(setting);
memnew_placement(r_result, Variant(result));
}
+void godot_icall_Globals_EditorShortcutOverride(const godot_string *p_setting, const godot_string *p_feature, Key p_keycode, bool p_physical) {
+ String setting = *reinterpret_cast<const String *>(p_setting);
+ String feature = *reinterpret_cast<const String *>(p_feature);
+ ED_SHORTCUT_OVERRIDE(setting, feature, p_keycode, p_physical);
+}
+
void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) {
String text = *reinterpret_cast<const String *>(p_text);
memnew_placement(r_dest, String(TTR(text)));
@@ -251,12 +268,15 @@ static const void *unmanaged_callbacks[]{
(void *)godot_icall_Internal_EditorNodeShowScriptScreen,
(void *)godot_icall_Internal_EditorRunPlay,
(void *)godot_icall_Internal_EditorRunStop,
+ (void *)godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar,
(void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts,
(void *)godot_icall_Internal_CodeCompletionRequest,
(void *)godot_icall_Globals_EditorScale,
(void *)godot_icall_Globals_GlobalDef,
(void *)godot_icall_Globals_EditorDef,
- (void *)godot_icall_Globals_EditorShortcut,
+ (void *)godot_icall_Globals_EditorDefShortcut,
+ (void *)godot_icall_Globals_EditorGetShortcut,
+ (void *)godot_icall_Globals_EditorShortcutOverride,
(void *)godot_icall_Globals_TTR,
(void *)godot_icall_Utils_OS_GetPlatformName,
(void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess,
diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp
index e08823bbf7..4f15335c1e 100644
--- a/modules/mono/editor/hostfxr_resolver.cpp
+++ b/modules/mono/editor/hostfxr_resolver.cpp
@@ -80,7 +80,7 @@ SOFTWARE.
namespace {
String get_hostfxr_file_name() {
-#if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED)
+#if defined(WINDOWS_ENABLED)
return "hostfxr.dll";
#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
return "libhostfxr.dylib";
@@ -320,7 +320,12 @@ bool get_dotnet_root_from_env(String &r_dotnet_root) {
bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) {
String fxr_dir = path::join(p_dotnet_root, "host", "fxr");
- ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir);
+ if (!DirAccess::exists(fxr_dir)) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ ERR_PRINT("The host fxr folder does not exist: " + fxr_dir + ".");
+ }
+ return false;
+ }
return get_latest_fxr(fxr_dir, r_fxr_path);
}
diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig
index d4e71b1bd9..df4a6c2d0d 100644
--- a/modules/mono/glue/GodotSharp/.editorconfig
+++ b/modules/mono/glue/GodotSharp/.editorconfig
@@ -1,8 +1,17 @@
[**/Generated/**.cs]
-# Validate parameter is non-null before using it
+# CA1062: Validate parameter is non-null before using it
# Useful for generated code, as it disables nullable
dotnet_diagnostic.CA1062.severity = error
# CA1069: Enums should not have duplicate values
dotnet_diagnostic.CA1069.severity = none
# CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1708.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
+dotnet_diagnostic.CS1573.severity = none
+
+[GodotSharp/Core/**.cs]
+# CS1591: Missing XML comment for publicly visible type or member
+# TODO: Temporary change to not pollute the warnings, but we need to document public APIs
+dotnet_diagnostic.CS1591.severity = suggestion
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
index 41cea031ab..48b47b166a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
@@ -26,7 +26,7 @@ partial class AnimationNode
partial class CodeEdit
{
- /// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant})"/>
+ /// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant}, int)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void AddCodeCompletionOption(CodeCompletionKind type, string displayText, string insertText, Nullable<Color> textColor, Resource icon, Nullable<Variant> value)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index d25944dceb..cc99225a33 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -723,7 +723,7 @@ namespace Godot
/// <returns>A hash code for this AABB.</returns>
public override readonly int GetHashCode()
{
- return _position.GetHashCode() ^ _size.GetHashCode();
+ return HashCode.Combine(_position, _size);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 5163ea5113..10aeeae995 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -52,7 +52,7 @@ namespace Godot.Collections
/// </summary>
/// <param name="array">The objects to put in the new array.</param>
/// <returns>A new Godot Array.</returns>
- public Array(Variant[] array) : this()
+ public Array(Variant[] array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
@@ -68,7 +68,7 @@ namespace Godot.Collections
this[i] = array[i];
}
- public Array(Span<StringName> array) : this()
+ public Array(Span<StringName> array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
@@ -84,7 +84,7 @@ namespace Godot.Collections
this[i] = array[i];
}
- public Array(Span<NodePath> array) : this()
+ public Array(Span<NodePath> array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
@@ -100,7 +100,7 @@ namespace Godot.Collections
this[i] = array[i];
}
- public Array(Span<Rid> array) : this()
+ public Array(Span<Rid> array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
@@ -121,7 +121,7 @@ namespace Godot.Collections
// fine as long as the array is not mutated. However, Span does this type checking at
// instantiation, so it's not possible to use it even when not mutating anything.
// ReSharper disable once RedundantNameQualifier
- public Array(ReadOnlySpan<GodotObject> array) : this()
+ public Array(ReadOnlySpan<GodotObject> array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
@@ -1057,7 +1057,7 @@ namespace Godot.Collections
/// </summary>
/// <param name="array">The items to put in the new array.</param>
/// <returns>A new Godot Array.</returns>
- public Array(T[] array) : this()
+ public Array(T[] array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index d53dd9a9af..a7712db737 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -1123,7 +1123,7 @@ namespace Godot
/// <returns>A hash code for this basis.</returns>
public override readonly int GetHashCode()
{
- return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode();
+ return HashCode.Combine(Row0, Row1, Row2);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 109643c2d4..a78cb0bba9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -25,10 +25,11 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
- public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+ public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
+ public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> ScriptManagerBridge_CallStatic;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get;
@@ -70,6 +71,7 @@ namespace Godot.Bridge
ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues,
+ ScriptManagerBridge_CallStatic = &ScriptManagerBridge.CallStatic,
CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call,
CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set,
CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index dfae85b667..9a7e19024b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@@ -90,7 +91,7 @@ namespace Godot.Bridge
internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
IntPtr godotObject)
{
- // TODO: Optimize with source generators and delegate pointers
+ // TODO: Optimize with source generators and delegate pointers.
try
{
@@ -124,13 +125,15 @@ namespace Godot.Bridge
IntPtr godotObject,
godot_variant** args, int argCount)
{
- // TODO: Optimize with source generators and delegate pointers
+ // TODO: Optimize with source generators and delegate pointers.
try
{
// Performance is not critical here as this will be replaced with source generators.
Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+ Debug.Assert(!scriptType.IsAbstract, $"Cannot create script instance. The class '{scriptType.FullName}' is abstract.");
+
var ctor = scriptType
.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(c => c.GetParameters().Length == argCount)
@@ -146,7 +149,7 @@ namespace Godot.Bridge
else
{
throw new MissingMemberException(
- $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters.");
+ $"The class '{scriptType.FullName}' does not define a constructor that takes {argCount} parameters.");
}
}
@@ -276,8 +279,13 @@ namespace Godot.Bridge
if (wrapperType != null && IsStatic(wrapperType))
{
- // A static class means this is a Godot singleton class. If an instance is needed we use GodotObject.
- return typeof(GodotObject);
+ // A static class means this is a Godot singleton class. Try to get the Instance proxy type.
+ wrapperType = TypeGetProxyClass($"{nativeTypeNameStr}Instance");
+ if (wrapperType == null)
+ {
+ // Otherwise, fallback to GodotObject.
+ return typeof(GodotObject);
+ }
}
return wrapperType;
@@ -592,7 +600,7 @@ namespace Godot.Bridge
[UnmanagedCallersOnly]
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName,
- godot_bool* outTool, godot_bool* outGlobal, godot_string* outIconPath,
+ godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath,
godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
{
@@ -626,9 +634,10 @@ namespace Godot.Bridge
var iconAttr = scriptType.GetCustomAttributes(inherit: false)
.OfType<IconAttribute>()
.FirstOrDefault();
-
*outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ *outAbstract = scriptType.IsAbstract.ToGodotBool();
+
// Methods
// Performance is not critical here as this will be replaced with source generators.
@@ -672,6 +681,8 @@ namespace Godot.Bridge
methodInfo.Add("params", methodParams);
+ methodInfo.Add("flags", (int)method.Flags);
+
methods.Add(methodInfo);
}
}
@@ -792,6 +803,7 @@ namespace Godot.Bridge
*outClassName = default;
*outTool = godot_bool.False;
*outGlobal = godot_bool.False;
+ *outAbstract = godot_bool.False;
*outIconPath = default;
*outMethodsDest = NativeFuncs.godotsharp_array_new();
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
@@ -953,6 +965,54 @@ namespace Godot.Bridge
public godot_variant Value; // Not owned
}
+ private delegate bool InvokeGodotClassStaticMethodDelegate(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret);
+
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool CallStatic(IntPtr scriptPtr, godot_string_name* method,
+ godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret)
+ {
+ // TODO: Optimize with source generators and delegate pointers.
+
+ try
+ {
+ Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+
+ Type? top = scriptType;
+ Type native = GodotObject.InternalGetClassNativeBase(top);
+
+ while (top != null && top != native)
+ {
+ var invokeGodotClassStaticMethod = top.GetMethod(
+ "InvokeGodotClassStaticMethod",
+ BindingFlags.DeclaredOnly | BindingFlags.Static |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ if (invokeGodotClassStaticMethod != null)
+ {
+ var invoked = invokeGodotClassStaticMethod.CreateDelegate<InvokeGodotClassStaticMethodDelegate>()(
+ CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args, argCount), out godot_variant retValue);
+ if (invoked)
+ {
+ *ret = retValue;
+ return godot_bool.True;
+ }
+ }
+
+ top = top.BaseType;
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ *ret = default;
+ return godot_bool.False;
+ }
+
+ *ret = default;
+ (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
+ return godot_bool.False;
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr,
delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
index 219a9a8c15..1239533a01 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
@@ -109,7 +109,8 @@ namespace Godot
}
godot_variant ret = NativeFuncs.godotsharp_callable_call(callable,
- (godot_variant**)argsPtr, argc, out _);
+ (godot_variant**)argsPtr, argc, out godot_variant_call_error vcall_error);
+ ExceptionUtils.DebugCheckCallError(callable, (godot_variant**)argsPtr, argc, vcall_error);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index 5dddb38055..293e680067 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -1308,7 +1308,7 @@ namespace Godot
/// <returns>A hash code for this color.</returns>
public override readonly int GetHashCode()
{
- return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode();
+ return HashCode.Combine(R, G, B, A);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
index c4161d2ded..57b292793a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
@@ -14,14 +14,46 @@ namespace Godot
{
private static void AppendTypeName(this StringBuilder sb, Type type)
{
- if (type.IsPrimitive)
- sb.Append(type.Name);
- else if (type == typeof(void))
+ // Use the C# type keyword for built-in types.
+ // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
+ if (type == typeof(void))
sb.Append("void");
+ else if (type == typeof(bool))
+ sb.Append("bool");
+ else if (type == typeof(byte))
+ sb.Append("byte");
+ else if (type == typeof(sbyte))
+ sb.Append("sbyte");
+ else if (type == typeof(char))
+ sb.Append("char");
+ else if (type == typeof(decimal))
+ sb.Append("decimal");
+ else if (type == typeof(double))
+ sb.Append("double");
+ else if (type == typeof(float))
+ sb.Append("float");
+ else if (type == typeof(int))
+ sb.Append("int");
+ else if (type == typeof(uint))
+ sb.Append("uint");
+ else if (type == typeof(nint))
+ sb.Append("nint");
+ else if (type == typeof(nuint))
+ sb.Append("nuint");
+ else if (type == typeof(long))
+ sb.Append("long");
+ else if (type == typeof(ulong))
+ sb.Append("ulong");
+ else if (type == typeof(short))
+ sb.Append("short");
+ else if (type == typeof(ushort))
+ sb.Append("ushort");
+ else if (type == typeof(object))
+ sb.Append("object");
+ else if (type == typeof(string))
+ sb.Append("string");
else
sb.Append(type);
-
- sb.Append(' ');
}
internal static void InstallTraceListener()
@@ -70,13 +102,26 @@ namespace Godot
}
}
+ internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
+ {
+ // We skip 2 frames:
+ // The first skipped frame is the current method.
+ // The second skipped frame is a method in NativeInterop.NativeFuncs.
+ var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true);
+ return stackTrace.GetFrame(0);
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetCurrentStackInfo(void* destVector)
{
try
{
var vector = (godot_stack_info_vector*)destVector;
- var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true);
+
+ // We skip 2 frames:
+ // The first skipped frame is the current method.
+ // The second skipped frame is a method in NativeInterop.NativeFuncs.
+ var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
int frameCount = stackTrace.FrameCount;
if (frameCount == 0)
@@ -87,6 +132,14 @@ namespace Godot
int i = 0;
foreach (StackFrame frame in stackTrace.GetFrames())
{
+ var method = frame.GetMethod();
+
+ if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute)))
+ {
+ // Skip methods marked hidden from the stack trace.
+ continue;
+ }
+
string? fileName = frame.GetFileName();
int fileLineNumber = frame.GetFileLineNumber();
@@ -102,6 +155,9 @@ namespace Godot
i++;
}
+
+ // Resize the vector again in case we skipped some frames.
+ vector->Resize(i);
}
catch (Exception e)
{
@@ -122,7 +178,10 @@ namespace Godot
var sb = new StringBuilder();
if (methodBase is MethodInfo methodInfo)
+ {
sb.AppendTypeName(methodInfo.ReturnType);
+ sb.Append(' ');
+ }
sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>");
sb.Append('.');
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
index 9425b7424c..33ebb8171e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Text;
using Godot.NativeInterop;
@@ -334,6 +335,21 @@ namespace Godot
NativeFuncs.godotsharp_printt(godotStr);
}
+ [StackTraceHidden]
+ private static void ErrPrintError(string message, godot_error_handler_type type = godot_error_handler_type.ERR_HANDLER_ERROR)
+ {
+ // Skip 1 frame to avoid current method.
+ var stackFrame = DebuggingUtils.GetCurrentStackFrame(skipFrames: 1);
+ string callerFilePath = ProjectSettings.LocalizePath(stackFrame.GetFileName());
+ DebuggingUtils.GetStackFrameMethodDecl(stackFrame, out string callerName);
+ int callerLineNumber = stackFrame.GetFileLineNumber();
+
+ using godot_string messageStr = Marshaling.ConvertStringToNative(message);
+ using godot_string callerNameStr = Marshaling.ConvertStringToNative(callerName);
+ using godot_string callerFilePathStr = Marshaling.ConvertStringToNative(callerFilePath);
+ NativeFuncs.godotsharp_err_print_error(callerNameStr, callerFilePathStr, callerLineNumber, messageStr, p_type: type);
+ }
+
/// <summary>
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
///
@@ -347,8 +363,7 @@ namespace Godot
/// <param name="message">Error message.</param>
public static void PushError(string message)
{
- using var godotStr = Marshaling.ConvertStringToNative(message);
- NativeFuncs.godotsharp_pusherror(godotStr);
+ ErrPrintError(message);
}
/// <summary>
@@ -364,7 +379,7 @@ namespace Godot
/// <param name="what">Arguments that form the error message.</param>
public static void PushError(params object[] what)
{
- PushError(AppendPrintParams(what));
+ ErrPrintError(AppendPrintParams(what));
}
/// <summary>
@@ -378,8 +393,7 @@ namespace Godot
/// <param name="message">Warning message.</param>
public static void PushWarning(string message)
{
- using var godotStr = Marshaling.ConvertStringToNative(message);
- NativeFuncs.godotsharp_pushwarning(godotStr);
+ ErrPrintError(message, type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
@@ -393,7 +407,7 @@ namespace Godot
/// <param name="what">Arguments that form the warning message.</param>
public static void PushWarning(params object[] what)
{
- PushWarning(AppendPrintParams(what));
+ ErrPrintError(AppendPrintParams(what), type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
index c6337e56ef..43598ca84d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs
@@ -247,6 +247,18 @@ namespace Godot
return methodBind;
}
+ internal static IntPtr ClassDB_get_method_with_compatibility(StringName type, StringName method, ulong hash)
+ {
+ var typeSelf = (godot_string_name)type.NativeValue;
+ var methodSelf = (godot_string_name)method.NativeValue;
+ IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method_with_compatibility(typeSelf, methodSelf, hash);
+
+ if (methodBind == IntPtr.Zero)
+ throw new NativeMethodBindNotFoundException(type + "." + method);
+
+ return methodBind;
+ }
+
internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type)
{
// for some reason the '??' operator doesn't support 'delegate*'
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
index ca0032df73..09269508b7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
@@ -104,6 +104,68 @@ namespace Godot
}
/// <summary>
+ /// Returns the hyperbolic arc (also called inverse) cosine of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's cosine in hyperbolic space if
+ /// <paramref name="s"/> is larger or equal to 1.
+ /// </summary>
+ /// <param name="s">The input hyperbolic cosine value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic cosine value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Acosh(float s)
+ {
+ return MathF.Acosh(s);
+ }
+
+ /// <summary>
+ /// Returns the hyperbolic arc (also called inverse) cosine of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's cosine in hyperbolic space if
+ /// <paramref name="s"/> is larger or equal to 1.
+ /// </summary>
+ /// <param name="s">The input hyperbolic cosine value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic cosine value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Acosh(double s)
+ {
+ return Math.Acosh(s);
+ }
+
+ /// <summary>
+ /// Returns the difference between the two angles,
+ /// in range of -<see cref="Pi"/>, <see cref="Pi"/>.
+ /// When <paramref name="from"/> and <paramref name="to"/> are opposite,
+ /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>,
+ /// or <see cref="Pi"/> otherwise.
+ /// </summary>
+ /// <param name="from">The start angle.</param>
+ /// <param name="to">The destination angle.</param>
+ /// <returns>The difference between the two angles.</returns>
+ public static float AngleDifference(float from, float to)
+ {
+ float difference = (to - from) % MathF.Tau;
+ return ((2.0f * difference) % MathF.Tau) - difference;
+ }
+
+ /// <summary>
+ /// Returns the difference between the two angles,
+ /// in range of -<see cref="Pi"/>, <see cref="Pi"/>.
+ /// When <paramref name="from"/> and <paramref name="to"/> are opposite,
+ /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>,
+ /// or <see cref="Pi"/> otherwise.
+ /// </summary>
+ /// <param name="from">The start angle.</param>
+ /// <param name="to">The destination angle.</param>
+ /// <returns>The difference between the two angles.</returns>
+ public static double AngleDifference(double from, double to)
+ {
+ double difference = (to - from) % Math.Tau;
+ return ((2.0 * difference) % Math.Tau) - difference;
+ }
+
+ /// <summary>
/// Returns the arc sine of <paramref name="s"/> in radians.
/// Use to get the angle of sine <paramref name="s"/>.
/// </summary>
@@ -132,6 +194,36 @@ namespace Godot
}
/// <summary>
+ /// Returns the hyperbolic arc (also called inverse) sine of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's sine in hyperbolic space if
+ /// <paramref name="s"/> is larger or equal to 1.
+ /// </summary>
+ /// <param name="s">The input hyperbolic sine value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic sine value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Asinh(float s)
+ {
+ return MathF.Asinh(s);
+ }
+
+ /// <summary>
+ /// Returns the hyperbolic arc (also called inverse) sine of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's sine in hyperbolic space if
+ /// <paramref name="s"/> is larger or equal to 1.
+ /// </summary>
+ /// <param name="s">The input hyperbolic sine value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic sine value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Asinh(double s)
+ {
+ return Math.Asinh(s);
+ }
+
+ /// <summary>
/// Returns the arc tangent of <paramref name="s"/> in radians.
/// Use to get the angle of tangent <paramref name="s"/>.
///
@@ -202,6 +294,36 @@ namespace Godot
}
/// <summary>
+ /// Returns the hyperbolic arc (also called inverse) tangent of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's tangent in hyperbolic space if
+ /// <paramref name="s"/> is between -1 and 1 (non-inclusive).
+ /// </summary>
+ /// <param name="s">The input hyperbolic tangent value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic tangent value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Atanh(float s)
+ {
+ return MathF.Atanh(s);
+ }
+
+ /// <summary>
+ /// Returns the hyperbolic arc (also called inverse) tangent of <paramref name="s"/> in radians.
+ /// Use it to get the angle from an angle's tangent in hyperbolic space if
+ /// <paramref name="s"/> is between -1 and 1 (non-inclusive).
+ /// </summary>
+ /// <param name="s">The input hyperbolic tangent value.</param>
+ /// <returns>
+ /// An angle that would result in the given hyperbolic tangent value.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Atanh(double s)
+ {
+ return Math.Atanh(s);
+ }
+
+ /// <summary>
/// Rounds <paramref name="s"/> upward (towards positive infinity).
/// </summary>
/// <param name="s">The number to ceil.</param>
@@ -1003,9 +1125,7 @@ namespace Godot
/// <returns>The resulting angle of the interpolation.</returns>
public static float LerpAngle(float from, float to, float weight)
{
- float difference = (to - from) % MathF.Tau;
- float distance = ((2 * difference) % MathF.Tau) - difference;
- return from + (distance * weight);
+ return from + AngleDifference(from, to) * weight;
}
/// <summary>
@@ -1020,9 +1140,7 @@ namespace Godot
/// <returns>The resulting angle of the interpolation.</returns>
public static double LerpAngle(double from, double to, double weight)
{
- double difference = (to - from) % Math.Tau;
- double distance = ((2 * difference) % Math.Tau) - difference;
- return from + (distance * weight);
+ return from + AngleDifference(from, to) * weight;
}
/// <summary>
@@ -1339,6 +1457,38 @@ namespace Godot
}
/// <summary>
+ /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>.
+ /// Similar to <see cref="MoveToward(float, float, float)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>.
+ /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle.
+ /// </summary>
+ /// <param name="from">The start angle.</param>
+ /// <param name="to">The angle to move towards.</param>
+ /// <param name="delta">The amount to move by.</param>
+ /// <returns>The angle after moving.</returns>
+ public static float RotateToward(float from, float to, float delta)
+ {
+ float difference = AngleDifference(from, to);
+ float absDifference = Math.Abs(difference);
+ return from + Math.Clamp(delta, absDifference - MathF.PI, absDifference) * (difference >= 0.0f ? 1.0f : -1.0f);
+ }
+
+ /// <summary>
+ /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>.
+ /// Similar to <see cref="MoveToward(double, double, double)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>.
+ /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle.
+ /// </summary>
+ /// <param name="from">The start angle.</param>
+ /// <param name="to">The angle to move towards.</param>
+ /// <param name="delta">The amount to move by.</param>
+ /// <returns>The angle after moving.</returns>
+ public static double RotateToward(double from, double to, double delta)
+ {
+ double difference = AngleDifference(from, to);
+ double absDifference = Math.Abs(difference);
+ return from + Math.Clamp(delta, absDifference - Math.PI, absDifference) * (difference >= 0.0 ? 1.0 : -1.0);
+ }
+
+ /// <summary>
/// Rounds <paramref name="s"/> to the nearest whole number,
/// with halfway cases rounded towards the nearest multiple of two.
/// </summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index 2d8067d300..dc53e48bd0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -95,7 +95,7 @@ namespace Godot.NativeInterop
}
NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line,
- nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector);
+ nErrorMsg, nExcMsg, godot_error_handler_type.ERR_HANDLER_ERROR, stackInfoVector);
}
}
@@ -135,5 +135,109 @@ namespace Godot.NativeInterop
OnExceptionLoggerException(unexpected, e);
}
}
+
+ [Conditional("DEBUG")]
+ public unsafe static void DebugCheckCallError(godot_string_name method, IntPtr instance, godot_variant** args, int argCount, godot_variant_call_error error)
+ {
+ if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
+ {
+ using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance);
+ string where = GetCallErrorWhere(method, &instanceVariant, args, argCount);
+ string errorText = GetCallErrorMessage(error, where, args);
+ GD.PushError(errorText);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public unsafe static void DebugCheckCallError(in godot_callable callable, godot_variant** args, int argCount, godot_variant_call_error error)
+ {
+ if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
+ {
+ using godot_variant callableVariant = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(callable);
+ string where = $"callable '{VariantUtils.ConvertToString(callableVariant)}'";
+ string errorText = GetCallErrorMessage(error, where, args);
+ GD.PushError(errorText);
+ }
+ }
+
+ private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount)
+ {
+ string? methodstr = null;
+ string basestr = GetVariantTypeName(instance);
+
+ if (method == GodotObject.MethodName.Call || (basestr == "Godot.TreeItem" && method == TreeItem.MethodName.CallRecursive))
+ {
+ if (argCount >= 1)
+ {
+ methodstr = VariantUtils.ConvertToString(*args[0]);
+ }
+ }
+
+ if (string.IsNullOrEmpty(methodstr))
+ {
+ methodstr = StringName.CreateTakingOwnershipOfDisposableValue(method);
+ }
+
+ return $"function '{methodstr}' in base '{basestr}'";
+ }
+
+ private unsafe static string GetCallErrorMessage(godot_variant_call_error error, string where, godot_variant** args)
+ {
+ switch (error.Error)
+ {
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT:
+ {
+ int errorarg = error.Argument;
+ // Handle the Object to Object case separately as we don't have further class details.
+#if DEBUG
+ if (error.Expected == Variant.Type.Object && args[errorarg]->Type == error.Expected)
+ {
+ return $"Invalid type in {where}. The Object-derived class of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") is not a subclass of the expected argument class.";
+ }
+ else if (error.Expected == Variant.Type.Array && args[errorarg]->Type == error.Expected)
+ {
+ return $"Invalid type in {where}. The array of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") does not have the same element type as the expected typed array argument.";
+ }
+ else
+#endif
+ {
+ return $"Invalid type in {where}. Cannot convert argument {errorarg + 1} from {args[errorarg]->Type} to {error.Expected}.";
+ }
+ }
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS:
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS:
+ return $"Invalid call to {where}. Expected {error.Expected} arguments.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD:
+ return $"Invalid call. Nonexistent {where}.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL:
+ return $"Attempt to call {where} on a null instance.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST:
+ return $"Attempt to call {where} on a const instance.";
+ default:
+ return $"Bug, call error: #{error.Error}";
+ }
+ }
+
+ private unsafe static string GetVariantTypeName(godot_variant* variant)
+ {
+ if (variant->Type == Variant.Type.Object)
+ {
+ GodotObject obj = VariantUtils.ConvertToGodotObject(*variant);
+ if (obj == null)
+ {
+ return "null instance";
+ }
+ else if (!GodotObject.IsInstanceValid(obj))
+ {
+ return "previously freed";
+ }
+ else
+ {
+ return obj.GetType().ToString();
+ }
+ }
+
+ return variant->Type.ToString();
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index 43e7c7eb9a..d5d9404ed1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -71,6 +71,7 @@ namespace Godot.NativeInterop
GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL,
+ GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST,
}
[StructLayout(LayoutKind.Sequential)]
@@ -1133,4 +1134,13 @@ namespace Godot.NativeInterop
get => _ptr != null ? *((int*)_ptr - 1) : 0;
}
}
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public enum godot_error_handler_type
+ {
+ ERR_HANDLER_ERROR = 0,
+ ERR_HANDLER_WARNING,
+ ERR_HANDLER_SCRIPT,
+ ERR_HANDLER_SHADER,
+ }
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index 3ec3d1e530..d181bf2c0f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -42,6 +42,9 @@ namespace Godot.NativeInterop
public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname,
in godot_string_name p_methodname);
+ public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility(
+ in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash);
+
public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor(
in godot_string_name p_classname);
@@ -58,7 +61,7 @@ namespace Godot.NativeInterop
internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func,
in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr,
- godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
+ godot_error_handler_type p_type, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
internal static partial godot_bool godotsharp_internal_script_debugger_is_active();
@@ -540,9 +543,7 @@ namespace Godot.NativeInterop
internal static partial void godotsharp_var_to_str(in godot_variant p_var, out godot_string r_ret);
- internal static partial void godotsharp_pusherror(in godot_string p_str);
-
- internal static partial void godotsharp_pushwarning(in godot_string p_str);
+ internal static partial void godotsharp_err_print_error(in godot_string p_function, in godot_string p_file, int p_line, in godot_string p_error, in godot_string p_message = default, godot_bool p_editor_notify = godot_bool.False, godot_error_handler_type p_type = godot_error_handler_type.ERR_HANDLER_ERROR);
// Object
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
index 3c7455a76c..85b2b02c45 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
@@ -414,7 +414,7 @@ namespace Godot
/// <returns>A hash code for this plane.</returns>
public override readonly int GetHashCode()
{
- return _normal.GetHashCode() ^ _d.GetHashCode();
+ return HashCode.Combine(_normal, _d);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
index 998a2786a7..a80d202ef2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
@@ -1001,7 +1001,7 @@ namespace Godot
/// <returns>A hash code for this projection.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode();
+ return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
index 2e282447bd..39e1b7e4b8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
@@ -800,7 +800,7 @@ namespace Godot
/// <returns>A hash code for this quaternion.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode();
+ return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index 458802f95d..babb26960b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -459,7 +459,7 @@ namespace Godot
/// <returns>A hash code for this rect.</returns>
public override readonly int GetHashCode()
{
- return _position.GetHashCode() ^ _size.GetHashCode();
+ return HashCode.Combine(_position, _size);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index 2099d0abca..49fba02b54 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -419,7 +419,7 @@ namespace Godot
/// <returns>A hash code for this rect.</returns>
public override readonly int GetHashCode()
{
- return _position.GetHashCode() ^ _size.GetHashCode();
+ return HashCode.Combine(_position, _size);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 618c892681..0e3e54a0c2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -626,7 +626,7 @@ namespace Godot
/// <returns>A hash code for this transform.</returns>
public override readonly int GetHashCode()
{
- return X.GetHashCode() ^ Y.GetHashCode() ^ Origin.GetHashCode();
+ return HashCode.Combine(X, Y, Origin);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
index b16e6e592e..7b27071df1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
@@ -653,7 +653,7 @@ namespace Godot
/// <returns>A hash code for this transform.</returns>
public override readonly int GetHashCode()
{
- return Basis.GetHashCode() ^ Origin.GetHashCode();
+ return HashCode.Combine(Basis, Origin);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 642ef231f3..4842dbc9af 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -1000,7 +1000,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode();
+ return HashCode.Combine(X, Y);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
index 231e791904..4ee452455e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs
@@ -182,6 +182,9 @@ 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 _zero = new Vector2I(0, 0);
private static readonly Vector2I _one = new Vector2I(1, 1);
@@ -191,6 +194,17 @@ namespace Godot
private static readonly Vector2I _left = new Vector2I(-1, 0);
/// <summary>
+ /// 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; } }
+ /// <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; } }
+
+ /// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(0, 0)</c>.</value>
@@ -542,7 +556,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode();
+ return HashCode.Combine(X, Y);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index 7d548f1d10..0b203c5148 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
@@ -1102,7 +1102,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode();
+ return HashCode.Combine(X, Y, Z);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
index 8543052f56..db8ceb30e9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs
@@ -193,6 +193,9 @@ 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 _zero = new Vector3I(0, 0, 0);
private static readonly Vector3I _one = new Vector3I(1, 1, 1);
@@ -204,6 +207,17 @@ namespace Godot
private static readonly Vector3I _back = new Vector3I(0, 0, 1);
/// <summary>
+ /// 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; } }
+ /// <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; } }
+
+ /// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, 0, 0)</c>.</value>
@@ -597,7 +611,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode();
+ return HashCode.Combine(X, Y, Z);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
index 10a0b14162..eeaef5e46e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs
@@ -884,7 +884,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode();
+ return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
index f813903177..e75e996b04 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs
@@ -228,10 +228,24 @@ 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 _zero = new Vector4I(0, 0, 0, 0);
private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1);
/// <summary>
+ /// 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; } }
+ /// <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; } }
+
+ /// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(0, 0, 0, 0)</c>.</value>
@@ -618,7 +632,7 @@ namespace Godot
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
- return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode();
+ return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 8a36b3e514..a55b8d693b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -11,9 +11,6 @@
<LangVersion>10</LangVersion>
<AnalysisMode>Recommended</AnalysisMode>
-
- <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. -->
- <NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Description>Godot C# Core API.</Description>
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index ee4de4e9f5..3518507f8c 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -68,6 +68,10 @@ MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, con
return ClassDB::get_method(*p_classname, *p_methodname);
}
+MethodBind *godotsharp_method_bind_get_method_with_compatibility(const StringName *p_classname, const StringName *p_methodname, uint64_t p_hash) {
+ return ClassDB::get_method_with_compatibility(*p_classname, *p_methodname, p_hash);
+}
+
godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) {
ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname);
if (class_info) {
@@ -92,10 +96,10 @@ void godotsharp_stack_info_vector_destroy(
void godotsharp_internal_script_debugger_send_error(const String *p_func,
const String *p_file, int32_t p_line, const String *p_err, const String *p_descr,
- bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) {
+ ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) {
const String file = ProjectSettings::get_singleton()->localize_path(p_file->simplify_path());
EngineDebugger::get_script_debugger()->send_error(*p_func, file, p_line, *p_err, *p_descr,
- true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector);
+ true, p_type, *p_stack_info_vector);
}
bool godotsharp_internal_script_debugger_is_active() {
@@ -1320,12 +1324,14 @@ void godotsharp_printraw(const godot_string *p_what) {
OS::get_singleton()->print("%s", reinterpret_cast<const String *>(p_what)->utf8().get_data());
}
-void godotsharp_pusherror(const godot_string *p_str) {
- ERR_PRINT(*reinterpret_cast<const String *>(p_str));
-}
-
-void godotsharp_pushwarning(const godot_string *p_str) {
- WARN_PRINT(*reinterpret_cast<const String *>(p_str));
+void godotsharp_err_print_error(const godot_string *p_function, const godot_string *p_file, int32_t p_line, const godot_string *p_error, const godot_string *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
+ _err_print_error(
+ reinterpret_cast<const String *>(p_function)->utf8().get_data(),
+ reinterpret_cast<const String *>(p_file)->utf8().get_data(),
+ p_line,
+ reinterpret_cast<const String *>(p_error)->utf8().get_data(),
+ reinterpret_cast<const String *>(p_message)->utf8().get_data(),
+ p_editor_notify, p_type);
}
void godotsharp_var_to_str(const godot_variant *p_var, godot_string *r_ret) {
@@ -1414,6 +1420,7 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) {
static const void *unmanaged_callbacks[]{
(void *)godotsharp_dotnet_module_is_initialized,
(void *)godotsharp_method_bind_get_method,
+ (void *)godotsharp_method_bind_get_method_with_compatibility,
(void *)godotsharp_get_class_constructor,
(void *)godotsharp_engine_get_singleton,
(void *)godotsharp_stack_info_vector_resize,
@@ -1611,8 +1618,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_str_to_var,
(void *)godotsharp_var_to_bytes,
(void *)godotsharp_var_to_str,
- (void *)godotsharp_pusherror,
- (void *)godotsharp_pushwarning,
+ (void *)godotsharp_err_print_error,
(void *)godotsharp_object_to_string,
};
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 279b5cfed2..80e44011be 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -33,10 +33,6 @@
#include "mono_gd/gd_mono.h"
#include "utils/path_utils.h"
-#ifdef ANDROID_ENABLED
-#include "mono_gd/support/android_support.h"
-#endif
-
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/os/os.h"
@@ -95,6 +91,36 @@ String _get_mono_user_dir() {
#endif
}
+#if !TOOLS_ENABLED
+// This should be the equivalent of GodotTools.Utils.OS.PlatformNameMap.
+static const char *platform_name_map[][2] = {
+ { "Windows", "windows" },
+ { "macOS", "macos" },
+ { "Linux", "linuxbsd" },
+ { "FreeBSD", "linuxbsd" },
+ { "NetBSD", "linuxbsd" },
+ { "BSD", "linuxbsd" },
+ { "Android", "android" },
+ { "iOS", "ios" },
+ { "Web", "web" },
+ { nullptr, nullptr }
+};
+
+String _get_platform_name() {
+ String platform_name = OS::get_singleton()->get_name();
+
+ int idx = 0;
+ while (platform_name_map[idx][0] != nullptr) {
+ if (platform_name_map[idx][0] == platform_name) {
+ return platform_name_map[idx][1];
+ }
+ idx++;
+ }
+
+ return "";
+}
+#endif
+
class _GodotSharpDirs {
public:
String res_metadata_dir;
@@ -139,12 +165,13 @@ private:
#endif
api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
#else // TOOLS_ENABLED
+ String platform = _get_platform_name();
String arch = Engine::get_singleton()->get_architecture_name();
String appname_safe = path::get_csharp_project_name();
String packed_path = "res://.godot/mono/publish/" + arch;
if (DirAccess::exists(packed_path)) {
// The dotnet publish data is packed in the pck/zip.
- String data_dir_root = OS::get_singleton()->get_cache_path().path_join("data_" + appname_safe + "_" + arch);
+ String data_dir_root = OS::get_singleton()->get_cache_path().path_join("data_" + appname_safe + "_" + platform + "_" + arch);
bool has_data = false;
if (!has_data) {
// 1. Try to access the data directly.
@@ -173,16 +200,10 @@ private:
api_assemblies_dir = data_dir_root;
} else {
// The dotnet publish data is in a directory next to the executable.
- String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch);
- if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = exe_dir.path_join("data_Godot_" + arch);
- }
+ String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + platform + "_" + arch);
#ifdef MACOS_ENABLED
if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch);
- }
- if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = res_dir.path_join("data_Godot_" + arch);
+ data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + platform + "_" + arch);
}
#endif
api_assemblies_dir = data_dir_root;
diff --git a/modules/mono/icons/BuildCSharp.svg b/modules/mono/icons/BuildCSharp.svg
new file mode 100644
index 0000000000..d4ba5bc293
--- /dev/null
+++ b/modules/mono/icons/BuildCSharp.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.256 4.85 1.7 2.945 2.598-1.5 4.5 7.794a1 1-30 0 0 2.598-1.5l-4.65-8.054 3.464-2-.25-.433s-1.25-2.165-3.848-.665l-3.464 2-.31.063z" fill="#e0e0e0"/></svg>
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 5e52f25cf4..247968e251 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -376,6 +376,12 @@ void GDMono::initialize() {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+ // Check that the .NET assemblies directory exists before trying to use it.
+ if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
+ OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
+ ERR_FAIL_MSG(".NET: Assemblies not found");
+ }
+
if (!load_hostfxr(hostfxr_dll_handle)) {
#if !defined(TOOLS_ENABLED)
godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index 8fdf163b26..ef4e32e4a7 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -42,7 +42,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
#define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \
{ \
- ERR_FAIL_COND_MSG(m_var == nullptr, \
+ ERR_FAIL_NULL_MSG(m_var, \
"Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \
checked_count += 1; \
}
@@ -70,6 +70,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues);
+ CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CallStatic);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index f604e4d681..dac8cdcaef 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -91,10 +91,11 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
- using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
+ using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
+ using FuncScriptManagerBridge_CallStatic = bool(GD_CLR_STDCALL *)(const CSharpScript *, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *);
using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *);
using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *);
using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *);
@@ -130,6 +131,7 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues;
+ FuncScriptManagerBridge_CallStatic ScriptManagerBridge_CallStatic;
FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call;
FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set;
FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get;
diff --git a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
index b976eea30b..1a51e4b6e9 100644
--- a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
+++ b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
@@ -37,6 +37,13 @@
Finds the index of the given [param path].
</description>
</method>
+ <method name="property_get_replication_mode">
+ <return type="int" enum="SceneReplicationConfig.ReplicationMode" />
+ <param index="0" name="path" type="NodePath" />
+ <description>
+ Returns the replication mode for the property identified by the given [param path]. See [enum ReplicationMode].
+ </description>
+ </method>
<method name="property_get_spawn">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
@@ -44,18 +51,28 @@
Returns whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
- <method name="property_get_sync">
+ <method name="property_get_sync" is_deprecated="true">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
Returns whether the property identified by the given [param path] is configured to be synchronized on process.
+ [i]Deprecated.[/i] Use [method property_get_replication_mode] instead.
</description>
</method>
- <method name="property_get_watch">
+ <method name="property_get_watch" is_deprecated="true">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
Returns whether the property identified by the given [param path] is configured to be reliably synchronized when changes are detected on process.
+ [i]Deprecated.[/i] Use [method property_get_replication_mode] instead.
+ </description>
+ </method>
+ <method name="property_set_replication_mode">
+ <return type="void" />
+ <param index="0" name="path" type="NodePath" />
+ <param index="1" name="mode" type="int" enum="SceneReplicationConfig.ReplicationMode" />
+ <description>
+ Sets the synchronization mode for the property identified by the given [param path]. See [enum ReplicationMode].
</description>
</method>
<method name="property_set_spawn">
@@ -66,20 +83,22 @@
Sets whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
- <method name="property_set_sync">
+ <method name="property_set_sync" is_deprecated="true">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
Sets whether the property identified by the given [param path] is configured to be synchronized on process.
+ [i]Deprecated.[/i] Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ALWAYS] instead.
</description>
</method>
- <method name="property_set_watch">
+ <method name="property_set_watch" is_deprecated="true">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
Sets whether the property identified by the given [param path] is configured to be reliably synchronized when changes are detected on process.
+ [i]Deprecated.[/i] Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ON_CHANGE] instead.
</description>
</method>
<method name="remove_property">
@@ -90,4 +109,15 @@
</description>
</method>
</methods>
+ <constants>
+ <constant name="REPLICATION_MODE_NEVER" value="0" enum="ReplicationMode">
+ Do not keep the given property synchronized.
+ </constant>
+ <constant name="REPLICATION_MODE_ALWAYS" value="1" enum="ReplicationMode">
+ Replicate the given property on process by constantly sending updates using unreliable transfer mode.
+ </constant>
+ <constant name="REPLICATION_MODE_ON_CHANGE" value="2" enum="ReplicationMode">
+ Replicate the given property on process by sending updates using reliable transfer mode when its value changes.
+ </constant>
+ </constants>
</class>
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index c2cb0a3d1a..a53eefc452 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -33,6 +33,7 @@
#include "core/os/os.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
void EditorNetworkProfiler::_bind_methods() {
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
@@ -62,19 +63,19 @@ void EditorNetworkProfiler::_notification(int p_what) {
void EditorNetworkProfiler::_update_theme_item_cache() {
VBoxContainer::_update_theme_item_cache();
- theme_cache.node_icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons"));
- theme_cache.stop_icon = get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"));
- theme_cache.play_icon = get_theme_icon(SNAME("Play"), SNAME("EditorIcons"));
- theme_cache.clear_icon = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"));
+ theme_cache.node_icon = get_theme_icon(SNAME("Node"), EditorStringName(EditorIcons));
+ theme_cache.stop_icon = get_theme_icon(SNAME("Stop"), EditorStringName(EditorIcons));
+ theme_cache.play_icon = get_theme_icon(SNAME("Play"), EditorStringName(EditorIcons));
+ theme_cache.clear_icon = get_theme_icon(SNAME("Clear"), EditorStringName(EditorIcons));
- theme_cache.multiplayer_synchronizer_icon = get_theme_icon("MultiplayerSynchronizer", SNAME("EditorIcons"));
- theme_cache.instance_options_icon = get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons"));
+ theme_cache.multiplayer_synchronizer_icon = get_theme_icon("MultiplayerSynchronizer", EditorStringName(EditorIcons));
+ theme_cache.instance_options_icon = get_theme_icon(SNAME("InstanceOptions"), EditorStringName(EditorIcons));
- theme_cache.incoming_bandwidth_icon = get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons"));
- theme_cache.outgoing_bandwidth_icon = get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons"));
+ theme_cache.incoming_bandwidth_icon = get_theme_icon(SNAME("ArrowDown"), EditorStringName(EditorIcons));
+ theme_cache.outgoing_bandwidth_icon = get_theme_icon(SNAME("ArrowUp"), EditorStringName(EditorIcons));
- theme_cache.incoming_bandwidth_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
- theme_cache.outgoing_bandwidth_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+ theme_cache.incoming_bandwidth_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
+ theme_cache.outgoing_bandwidth_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
}
void EditorNetworkProfiler::_refresh() {
@@ -128,7 +129,7 @@ void EditorNetworkProfiler::refresh_replication_data() {
const NodeInfo &cfg_info = node_data[E.value.config];
node->set_text(0, root_info.path.get_file());
- node->set_icon(0, has_theme_icon(root_info.type, SNAME("EditorIcons")) ? get_theme_icon(root_info.type, SNAME("EditorIcons")) : theme_cache.node_icon);
+ node->set_icon(0, has_theme_icon(root_info.type, EditorStringName(EditorIcons)) ? get_theme_icon(root_info.type, EditorStringName(EditorIcons)) : theme_cache.node_icon);
node->set_tooltip_text(0, root_info.path);
node->set_text(1, sync_info.path.get_file());
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index 04aa856bf9..eab1f5d51d 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -35,6 +35,7 @@
#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"
@@ -111,9 +112,9 @@ void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) {
void ReplicationEditor::_pick_node_selected(NodePath p_path) {
Node *root = current->get_node(current->get_root_path());
- ERR_FAIL_COND(!root);
+ ERR_FAIL_NULL(root);
Node *node = get_node(p_path);
- ERR_FAIL_COND(!node);
+ ERR_FAIL_NULL(node);
NodePath path_to = root->get_path_to(node);
adding_node_path = path_to;
prop_selector->select_property_from_instance(node);
@@ -176,11 +177,6 @@ ReplicationEditor::ReplicationEditor() {
delete_dialog->connect("confirmed", callable_mp(this, &ReplicationEditor::_dialog_closed).bind(true));
add_child(delete_dialog);
- error_dialog = memnew(AcceptDialog);
- error_dialog->set_ok_button_text(TTR("Close"));
- error_dialog->set_title(TTR("Error!"));
- add_child(error_dialog);
-
VBoxContainer *vb = memnew(VBoxContainer);
vb->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(vb);
@@ -249,42 +245,45 @@ ReplicationEditor::ReplicationEditor() {
add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property));
add_pick_button->set_text(TTR("Add property to sync..."));
hb->add_child(add_pick_button);
+
VSeparator *vs = memnew(VSeparator);
vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0));
hb->add_child(vs);
hb->add_child(memnew(Label(TTR("Path:"))));
+
np_line_edit = memnew(LineEdit);
np_line_edit->set_placeholder(":property");
np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ np_line_edit->connect("text_submitted", callable_mp(this, &ReplicationEditor::_np_text_submitted));
hb->add_child(np_line_edit);
+
add_from_path_button = memnew(Button);
add_from_path_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed));
add_from_path_button->set_text(TTR("Add from path"));
hb->add_child(add_from_path_button);
+
vs = memnew(VSeparator);
vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0));
hb->add_child(vs);
+
pin = memnew(Button);
- pin->set_flat(true);
+ pin->set_theme_type_variation("FlatButton");
pin->set_toggle_mode(true);
hb->add_child(pin);
tree = memnew(Tree);
tree->set_hide_root(true);
- tree->set_columns(5);
+ tree->set_columns(4);
tree->set_column_titles_visible(true);
tree->set_column_title(0, TTR("Properties"));
tree->set_column_expand(0, true);
tree->set_column_title(1, TTR("Spawn"));
tree->set_column_expand(1, false);
tree->set_column_custom_minimum_width(1, 100);
- tree->set_column_title(2, TTR("Sync"));
+ tree->set_column_title(2, TTR("Replicate"));
tree->set_column_custom_minimum_width(2, 100);
- tree->set_column_title(3, TTR("Watch"));
- tree->set_column_custom_minimum_width(3, 100);
tree->set_column_expand(2, false);
tree->set_column_expand(3, false);
- tree->set_column_expand(4, false);
tree->create_item();
tree->connect("button_clicked", callable_mp(this, &ReplicationEditor::_tree_button_pressed));
tree->connect("item_edited", callable_mp(this, &ReplicationEditor::_tree_item_edited));
@@ -292,7 +291,7 @@ ReplicationEditor::ReplicationEditor() {
vb->add_child(tree);
drop_label = memnew(Label);
- drop_label->set_text(TTR("Add properties using the buttons above or\ndrag them them from the inspector and drop them here."));
+ drop_label->set_text(TTR("Add properties using the options above, or\ndrag them them from the inspector and drop them here."));
drop_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
drop_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
tree->add_child(drop_label);
@@ -303,7 +302,7 @@ ReplicationEditor::ReplicationEditor() {
void ReplicationEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_config"), &ReplicationEditor::_update_config);
- ClassDB::bind_method(D_METHOD("_update_checked", "property", "column", "checked"), &ReplicationEditor::_update_checked);
+ ClassDB::bind_method(D_METHOD("_update_value", "property", "column", "value"), &ReplicationEditor::_update_value);
}
bool ReplicationEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
@@ -365,25 +364,29 @@ void ReplicationEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel")));
- add_pick_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- pin->set_icon(get_theme_icon(SNAME("Pin"), SNAME("EditorIcons")));
+ add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel")));
+ add_pick_button->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ pin->set_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons)));
} break;
}
}
void ReplicationEditor::_add_pressed() {
if (!current) {
- error_dialog->set_text(TTR("Please select a MultiplayerSynchronizer first."));
- error_dialog->popup_centered();
+ EditorNode::get_singleton()->show_warning(TTR("Please select a MultiplayerSynchronizer first."));
return;
}
if (current->get_root_path().is_empty()) {
- error_dialog->set_text(TTR("The MultiplayerSynchronizer needs a root path."));
- error_dialog->popup_centered();
+ EditorNode::get_singleton()->show_warning(TTR("The MultiplayerSynchronizer needs a root path."));
return;
}
String np_text = np_line_edit->get_text();
+
+ if (np_text.is_empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("Property/path must not be empty."));
+ return;
+ }
+
int idx = np_text.find(":");
if (idx == -1) {
np_text = ".:" + np_text;
@@ -391,46 +394,54 @@ void ReplicationEditor::_add_pressed() {
np_text = "." + np_text;
}
NodePath path = NodePath(np_text);
+ if (path.is_empty()) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid property path: '%s'"), np_text));
+ return;
+ }
_add_sync_property(path);
}
+void ReplicationEditor::_np_text_submitted(const String &p_newtext) {
+ _add_pressed();
+}
+
void ReplicationEditor::_tree_item_edited() {
TreeItem *ti = tree->get_edited();
if (!ti || config.is_null()) {
return;
}
int column = tree->get_edited_column();
- ERR_FAIL_COND(column < 1 || column > 3);
+ ERR_FAIL_COND(column < 1 || column > 2);
const NodePath prop = ti->get_metadata(0);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- bool value = ti->is_checked(column);
- // We have a hard limit of 64 watchable properties per synchronizer.
- if (column == 3 && value && config->get_watch_properties().size() > 64) {
- error_dialog->set_text(TTR("Each MultiplayerSynchronizer can have no more than 64 watched properties."));
- error_dialog->popup_centered();
- ti->set_checked(column, false);
- return;
- }
- String method;
if (column == 1) {
undo_redo->create_action(TTR("Set spawn property"));
- method = "property_set_spawn";
+ bool value = ti->is_checked(column);
+ undo_redo->add_do_method(config.ptr(), "property_set_spawn", prop, value);
+ undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, !value);
+ undo_redo->add_do_method(this, "_update_value", prop, column, value ? 1 : 0);
+ undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 1 : 0);
+ undo_redo->commit_action();
} else if (column == 2) {
undo_redo->create_action(TTR("Set sync property"));
- method = "property_set_sync";
- } else if (column == 3) {
- undo_redo->create_action(TTR("Set watch property"));
- method = "property_set_watch";
+ int value = ti->get_range(column);
+ int old_value = config->property_get_replication_mode(prop);
+ // We have a hard limit of 64 watchable properties per synchronizer.
+ if (value == SceneReplicationConfig::REPLICATION_MODE_ON_CHANGE && config->get_watch_properties().size() >= 64) {
+ EditorNode::get_singleton()->show_warning(TTR("Each MultiplayerSynchronizer can have no more than 64 watched properties."));
+ ti->set_range(column, old_value);
+ return;
+ }
+ undo_redo->add_do_method(config.ptr(), "property_set_replication_mode", prop, value);
+ undo_redo->add_undo_method(config.ptr(), "property_set_replication_mode", prop, old_value);
+ undo_redo->add_do_method(this, "_update_value", prop, column, value);
+ undo_redo->add_undo_method(this, "_update_value", prop, column, old_value);
+ undo_redo->commit_action();
} else {
ERR_FAIL();
}
- undo_redo->add_do_method(config.ptr(), method, prop, value);
- undo_redo->add_undo_method(config.ptr(), method, prop, !value);
- undo_redo->add_do_method(this, "_update_checked", prop, column, value);
- undo_redo->add_undo_method(this, "_update_checked", prop, column, !value);
- undo_redo->commit_action();
}
void ReplicationEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
@@ -455,15 +466,13 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) {
const NodePath prop = deleting;
int idx = config->property_get_index(prop);
bool spawn = config->property_get_spawn(prop);
- bool sync = config->property_get_sync(prop);
- bool watch = config->property_get_watch(prop);
+ SceneReplicationConfig::ReplicationMode mode = config->property_get_replication_mode(prop);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove Property"));
undo_redo->add_do_method(config.ptr(), "remove_property", prop);
undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx);
undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, spawn);
- undo_redo->add_undo_method(config.ptr(), "property_set_sync", prop, sync);
- undo_redo->add_undo_method(config.ptr(), "property_set_watch", prop, watch);
+ undo_redo->add_undo_method(config.ptr(), "property_set_replication_mode", prop, mode);
undo_redo->add_do_method(this, "_update_config");
undo_redo->add_undo_method(this, "_update_config");
undo_redo->commit_action();
@@ -471,14 +480,18 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) {
deleting = NodePath();
}
-void ReplicationEditor::_update_checked(const NodePath &p_prop, int p_column, bool p_checked) {
+void ReplicationEditor::_update_value(const NodePath &p_prop, int p_column, int p_value) {
if (!tree->get_root()) {
return;
}
TreeItem *ti = tree->get_root()->get_first_child();
while (ti) {
if (ti->get_metadata(0).operator NodePath() == p_prop) {
- ti->set_checked(p_column, p_checked);
+ if (p_column == 1) {
+ ti->set_checked(p_column, p_value != 0);
+ } else if (p_column == 2) {
+ ti->set_range(p_column, p_value);
+ }
return;
}
ti = ti->get_next();
@@ -499,7 +512,7 @@ void ReplicationEditor::_update_config() {
}
for (int i = 0; i < props.size(); i++) {
const NodePath path = props[i];
- _add_property(path, config->property_get_spawn(path), config->property_get_sync(path), config->property_get_watch(path));
+ _add_property(path, config->property_get_spawn(path), config->property_get_replication_mode(path));
}
}
@@ -517,10 +530,10 @@ void ReplicationEditor::edit(MultiplayerSynchronizer *p_sync) {
}
Ref<Texture2D> ReplicationEditor::_get_class_icon(const Node *p_node) {
- if (!p_node || !has_theme_icon(p_node->get_class(), "EditorIcons")) {
- return get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons"));
+ if (!p_node || !has_theme_icon(p_node->get_class(), EditorStringName(EditorIcons))) {
+ return get_theme_icon(SNAME("ImportFail"), EditorStringName(EditorIcons));
}
- return get_theme_icon(p_node->get_class(), "EditorIcons");
+ return get_theme_icon(p_node->get_class(), EditorStringName(EditorIcons));
}
static bool can_sync(const Variant &p_var) {
@@ -541,14 +554,13 @@ static bool can_sync(const Variant &p_var) {
}
}
-void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, bool p_sync, bool p_watch) {
+void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode) {
String prop = String(p_property);
TreeItem *item = tree->create_item();
item->set_selectable(0, false);
item->set_selectable(1, false);
item->set_selectable(2, false);
item->set_selectable(3, false);
- item->set_selectable(4, false);
item->set_text(0, prop);
item->set_metadata(0, prop);
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
@@ -565,22 +577,23 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
bool valid = false;
Variant value = node->get(subpath, &valid);
if (valid && !can_sync(value)) {
- item->set_icon(3, get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")));
- item->set_tooltip_text(3, TTR("Property of this type not supported."));
+ item->set_icon(0, get_theme_icon(SNAME("StatusWarning"), EditorStringName(EditorIcons)));
+ item->set_tooltip_text(0, TTR("Property of this type not supported."));
+ } else {
+ item->set_icon(0, icon);
}
+ } else {
+ item->set_icon(0, icon);
}
- item->set_icon(0, icon);
- item->add_button(4, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ item->add_button(3, get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
item->set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER);
item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK);
item->set_checked(1, p_spawn);
item->set_editable(1, true);
item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER);
- item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK);
- item->set_checked(2, p_sync);
+ item->set_cell_mode(2, TreeItem::CELL_MODE_RANGE);
+ item->set_range_config(2, 0, 2, 1);
+ item->set_text(2, "Never,Always,On Change");
+ item->set_range(2, (int)p_mode);
item->set_editable(2, true);
- item->set_text_alignment(3, HORIZONTAL_ALIGNMENT_CENTER);
- item->set_cell_mode(3, TreeItem::CELL_MODE_CHECK);
- item->set_checked(3, p_watch);
- item->set_editable(3, true);
}
diff --git a/modules/multiplayer/editor/replication_editor.h b/modules/multiplayer/editor/replication_editor.h
index 208eaabff5..80c1892ec3 100644
--- a/modules/multiplayer/editor/replication_editor.h
+++ b/modules/multiplayer/editor/replication_editor.h
@@ -51,7 +51,6 @@ class ReplicationEditor : public VBoxContainer {
private:
MultiplayerSynchronizer *current = nullptr;
- AcceptDialog *error_dialog = nullptr;
ConfirmationDialog *delete_dialog = nullptr;
Button *add_pick_button = nullptr;
Button *add_from_path_button = nullptr;
@@ -72,12 +71,13 @@ private:
Ref<Texture2D> _get_class_icon(const Node *p_node);
void _add_pressed();
+ void _np_text_submitted(const String &p_newtext);
void _tree_item_edited();
void _tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
- void _update_checked(const NodePath &p_prop, int p_column, bool p_checked);
+ void _update_value(const NodePath &p_prop, int p_column, int p_checked);
void _update_config();
void _dialog_closed(bool p_confirmed);
- void _add_property(const NodePath &p_property, bool p_spawn = true, bool p_sync = true, bool p_watch = false);
+ void _add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode);
void _pick_node_filter_text_changed(const String &p_newtext);
void _pick_node_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates);
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
index ea52741601..9b05fa884b 100644
--- a/modules/multiplayer/multiplayer_debugger.cpp
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -237,7 +237,7 @@ void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_proces
// ReplicationProfiler
MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) {
- ERR_FAIL_COND(!p_sync);
+ ERR_FAIL_NULL(p_sync);
synchronizer = p_sync->get_instance_id();
if (p_sync->get_replication_config().is_valid()) {
config = p_sync->get_replication_config()->get_instance_id();
@@ -305,7 +305,7 @@ void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) {
const ObjectID id = p_data[1];
const uint64_t size = p_data[2];
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id));
- ERR_FAIL_COND(!sync);
+ ERR_FAIL_NULL(sync);
if (!sync_data.has(id)) {
sync_data[id] = SyncInfo(sync);
}
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 6c6aa28344..264a2e9c8e 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -268,7 +268,7 @@ void MultiplayerSpawner::_spawn_notify(ObjectID p_id) {
void MultiplayerSpawner::_node_exit(ObjectID p_id) {
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
- ERR_FAIL_COND(!node);
+ ERR_FAIL_NULL(node);
if (tracked_nodes.has(p_id)) {
tracked_nodes.erase(p_id);
get_multiplayer()->object_configuration_remove(node, this);
@@ -323,10 +323,10 @@ Node *MultiplayerSpawner::spawn(const Variant &p_data) {
ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires the 'spawn_function' property to be a valid callable.");
Node *parent = get_spawn_node();
- ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node.");
+ ERR_FAIL_NULL_V_MSG(parent, nullptr, "Cannot find spawn node.");
Node *node = instantiate_custom(p_data);
- ERR_FAIL_COND_V_MSG(!node, nullptr, "The 'spawn_function' callable must return a valid node.");
+ ERR_FAIL_NULL_V_MSG(node, nullptr, "The 'spawn_function' callable must return a valid node.");
_track(node, p_data);
parent->add_child(node, true);
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 9c2d281f72..233f15c3a4 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -149,14 +149,14 @@ PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const {
}
Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) {
- ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER);
r_variant.resize(p_properties.size());
r_variant_ptrs.resize(r_variant.size());
int i = 0;
for (const NodePath &prop : p_properties) {
bool valid = false;
const Object *obj = _get_prop_target(p_obj, prop);
- ERR_FAIL_COND_V(!obj, FAILED);
+ ERR_FAIL_NULL_V(obj, FAILED);
r_variant.write[i] = obj->get_indexed(prop.get_subnames(), &valid);
r_variant_ptrs.write[i] = &r_variant[i];
ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop));
@@ -166,11 +166,11 @@ Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Obj
}
Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) {
- ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER);
int i = 0;
for (const NodePath &prop : p_properties) {
Object *obj = _get_prop_target(p_obj, prop);
- ERR_FAIL_COND_V(!obj, FAILED);
+ ERR_FAIL_NULL_V(obj, FAILED);
obj->set_indexed(prop.get_subnames(), p_state[i]);
i += 1;
}
@@ -374,7 +374,7 @@ Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) {
return OK;
}
Node *node = get_root_node();
- ERR_FAIL_COND_V(!node, FAILED);
+ ERR_FAIL_NULL_V(node, FAILED);
int idx = -1;
Watcher *ptr = watchers.ptrw();
for (const NodePath &prop : props) {
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index 90e6ac7c2c..8d102ca981 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -53,7 +53,7 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
- ERR_FAIL_COND(!root_node);
+ ERR_FAIL_NULL(root_node);
ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small.");
int ofs = 1;
@@ -74,7 +74,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
}
Node *node = root_node->get_node(path);
- ERR_FAIL_COND(node == nullptr);
+ ERR_FAIL_NULL(node);
const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5;
if (valid_rpc_checksum == false) {
ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path);
@@ -119,7 +119,7 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack
}
PathSentCache *psc = path_send_cache.getptr(path);
- ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache.");
+ ERR_FAIL_NULL_MSG(psc, "Invalid packet received. Tries to confirm a path which was not found in cache.");
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.");
@@ -165,7 +165,7 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) {
const PathSentCache *psc = path_send_cache.getptr(p_path);
- ERR_FAIL_COND_V(!psc, false);
+ 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;
@@ -173,7 +173,7 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) {
int SceneCacheInterface::make_object_cache(Object *p_obj) {
Node *node = Object::cast_to<Node>(p_obj);
- ERR_FAIL_COND_V(!node, -1);
+ ERR_FAIL_NULL_V(node, -1);
NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path());
// See if the path is cached.
PathSentCache *psc = path_send_cache.getptr(for_path);
@@ -188,7 +188,7 @@ int SceneCacheInterface::make_object_cache(Object *p_obj) {
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_COND_V(!node, false);
+ ERR_FAIL_NULL_V(node, false);
r_id = make_object_cache(p_obj);
ERR_FAIL_COND_V(r_id < 0, false);
@@ -233,7 +233,7 @@ 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_COND_V(!root_node, nullptr);
+ 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));
diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h
index 9400417cdb..7a7304fde8 100644
--- a/modules/multiplayer/scene_cache_interface.h
+++ b/modules/multiplayer/scene_cache_interface.h
@@ -33,6 +33,7 @@
#include "scene/main/multiplayer_api.h"
+class Node;
class SceneMultiplayer;
class SceneCacheInterface : public RefCounted {
diff --git a/modules/multiplayer/scene_replication_config.cpp b/modules/multiplayer/scene_replication_config.cpp
index af6af35219..836fa1014d 100644
--- a/modules/multiplayer/scene_replication_config.cpp
+++ b/modules/multiplayer/scene_replication_config.cpp
@@ -47,38 +47,26 @@ bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_val
add_property(path);
return true;
}
- ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
ERR_FAIL_INDEX_V(idx, properties.size(), false);
ReplicationProperty &prop = properties[idx];
- if (what == "sync") {
- if ((bool)p_value == prop.sync) {
- return true;
- }
- prop.sync = p_value;
- if (prop.sync) {
- sync_props.push_back(prop.name);
- } else {
- sync_props.erase(prop.name);
- }
+ if (what == "replication_mode") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ ReplicationMode mode = (ReplicationMode)p_value.operator int();
+ ERR_FAIL_COND_V(mode < REPLICATION_MODE_NEVER || mode > REPLICATION_MODE_ON_CHANGE, false);
+ property_set_replication_mode(prop.name, mode);
return true;
- } else if (what == "spawn") {
- if ((bool)p_value == prop.spawn) {
- return true;
- }
- prop.spawn = p_value;
- if (prop.spawn) {
- spawn_props.push_back(prop.name);
- } else {
- spawn_props.erase(prop.name);
- }
+ }
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
+ if (what == "spawn") {
+ property_set_spawn(prop.name, p_value);
+ return true;
+ } else if (what == "sync") {
+ // Deprecated.
+ property_set_sync(prop.name, p_value);
return true;
} else if (what == "watch") {
- prop.watch = p_value;
- if (prop.watch) {
- watch_props.push_back(prop.name);
- } else {
- watch_props.erase(prop.name);
- }
+ // Deprecated.
+ property_set_watch(prop.name, p_value);
return true;
}
}
@@ -96,14 +84,11 @@ bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) cons
if (what == "path") {
r_ret = prop.name;
return true;
- } else if (what == "sync") {
- r_ret = prop.sync;
- return true;
} else if (what == "spawn") {
r_ret = prop.spawn;
return true;
- } else if (what == "watch") {
- r_ret = prop.watch;
+ } else if (what == "replication_mode") {
+ r_ret = prop.mode;
return true;
}
}
@@ -114,8 +99,7 @@ void SceneReplicationConfig::_get_property_list(List<PropertyInfo> *p_list) cons
for (int i = 0; i < properties.size(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/spawn", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/watch", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::INT, "properties/" + itos(i) + "/replication_mode", PROPERTY_HINT_ENUM, "Never,Always,On Change", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
}
@@ -129,11 +113,11 @@ TypedArray<NodePath> SceneReplicationConfig::get_properties() const {
void SceneReplicationConfig::add_property(const NodePath &p_path, int p_index) {
ERR_FAIL_COND(properties.find(p_path));
+ ERR_FAIL_COND(p_path == NodePath());
if (p_index < 0 || p_index == properties.size()) {
properties.push_back(ReplicationProperty(p_path));
- sync_props.push_back(p_path);
- spawn_props.push_back(p_path);
+ dirty = true;
return;
}
@@ -146,22 +130,12 @@ void SceneReplicationConfig::add_property(const NodePath &p_path, int p_index) {
c++;
}
properties.insert_before(I, ReplicationProperty(p_path));
- sync_props.clear();
- spawn_props.clear();
- for (const ReplicationProperty &prop : properties) {
- if (prop.sync) {
- sync_props.push_back(prop.name);
- }
- if (prop.spawn) {
- spawn_props.push_back(prop.name);
- }
- }
+ dirty = true;
}
void SceneReplicationConfig::remove_property(const NodePath &p_path) {
properties.erase(p_path);
- sync_props.erase(p_path);
- spawn_props.erase(p_path);
+ dirty = true;
}
bool SceneReplicationConfig::has_property(const NodePath &p_path) const {
@@ -195,56 +169,99 @@ void SceneReplicationConfig::property_set_spawn(const NodePath &p_path, bool p_e
return;
}
E->get().spawn = p_enabled;
- spawn_props.clear();
- for (const ReplicationProperty &prop : properties) {
- if (prop.spawn) {
- spawn_props.push_back(prop.name);
- }
- }
+ dirty = true;
}
bool SceneReplicationConfig::property_get_sync(const NodePath &p_path) {
List<ReplicationProperty>::Element *E = properties.find(p_path);
ERR_FAIL_COND_V(!E, false);
- return E->get().sync;
+ return E->get().mode == REPLICATION_MODE_ALWAYS;
}
void SceneReplicationConfig::property_set_sync(const NodePath &p_path, bool p_enabled) {
- List<ReplicationProperty>::Element *E = properties.find(p_path);
- ERR_FAIL_COND(!E);
- if (E->get().sync == p_enabled) {
- return;
- }
- E->get().sync = p_enabled;
- sync_props.clear();
- for (const ReplicationProperty &prop : properties) {
- if (prop.sync) {
- sync_props.push_back(prop.name);
- }
+ if (p_enabled) {
+ property_set_replication_mode(p_path, REPLICATION_MODE_ALWAYS);
+ } else if (property_get_replication_mode(p_path) == REPLICATION_MODE_ALWAYS) {
+ property_set_replication_mode(p_path, REPLICATION_MODE_NEVER);
}
}
bool SceneReplicationConfig::property_get_watch(const NodePath &p_path) {
List<ReplicationProperty>::Element *E = properties.find(p_path);
ERR_FAIL_COND_V(!E, false);
- return E->get().watch;
+ return E->get().mode == REPLICATION_MODE_ON_CHANGE;
}
void SceneReplicationConfig::property_set_watch(const NodePath &p_path, bool p_enabled) {
+ if (p_enabled) {
+ property_set_replication_mode(p_path, REPLICATION_MODE_ON_CHANGE);
+ } else if (property_get_replication_mode(p_path) == REPLICATION_MODE_ON_CHANGE) {
+ property_set_replication_mode(p_path, REPLICATION_MODE_NEVER);
+ }
+}
+
+SceneReplicationConfig::ReplicationMode SceneReplicationConfig::property_get_replication_mode(const NodePath &p_path) {
+ List<ReplicationProperty>::Element *E = properties.find(p_path);
+ ERR_FAIL_COND_V(!E, REPLICATION_MODE_NEVER);
+ return E->get().mode;
+}
+
+void SceneReplicationConfig::property_set_replication_mode(const NodePath &p_path, ReplicationMode p_mode) {
List<ReplicationProperty>::Element *E = properties.find(p_path);
ERR_FAIL_COND(!E);
- if (E->get().watch == p_enabled) {
+ if (E->get().mode == p_mode) {
return;
}
- E->get().watch = p_enabled;
+ E->get().mode = p_mode;
+ dirty = true;
+}
+
+void SceneReplicationConfig::_update() {
+ if (!dirty) {
+ return;
+ }
+ dirty = false;
+ sync_props.clear();
+ spawn_props.clear();
watch_props.clear();
for (const ReplicationProperty &prop : properties) {
- if (prop.watch) {
- watch_props.push_back(p_path);
+ if (prop.spawn) {
+ spawn_props.push_back(prop.name);
+ }
+ switch (prop.mode) {
+ case REPLICATION_MODE_ALWAYS:
+ sync_props.push_back(prop.name);
+ break;
+ case REPLICATION_MODE_ON_CHANGE:
+ watch_props.push_back(prop.name);
+ break;
+ default:
+ break;
}
}
}
+const List<NodePath> &SceneReplicationConfig::get_spawn_properties() {
+ if (dirty) {
+ _update();
+ }
+ return spawn_props;
+}
+
+const List<NodePath> &SceneReplicationConfig::get_sync_properties() {
+ if (dirty) {
+ _update();
+ }
+ return sync_props;
+}
+
+const List<NodePath> &SceneReplicationConfig::get_watch_properties() {
+ if (dirty) {
+ _update();
+ }
+ return watch_props;
+}
+
void SceneReplicationConfig::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_properties"), &SceneReplicationConfig::get_properties);
ClassDB::bind_method(D_METHOD("add_property", "path", "index"), &SceneReplicationConfig::add_property, DEFVAL(-1));
@@ -253,6 +270,14 @@ void SceneReplicationConfig::_bind_methods() {
ClassDB::bind_method(D_METHOD("property_get_index", "path"), &SceneReplicationConfig::property_get_index);
ClassDB::bind_method(D_METHOD("property_get_spawn", "path"), &SceneReplicationConfig::property_get_spawn);
ClassDB::bind_method(D_METHOD("property_set_spawn", "path", "enabled"), &SceneReplicationConfig::property_set_spawn);
+ ClassDB::bind_method(D_METHOD("property_get_replication_mode", "path"), &SceneReplicationConfig::property_get_replication_mode);
+ ClassDB::bind_method(D_METHOD("property_set_replication_mode", "path", "mode"), &SceneReplicationConfig::property_set_replication_mode);
+
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_NEVER);
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_ALWAYS);
+ BIND_ENUM_CONSTANT(REPLICATION_MODE_ON_CHANGE);
+
+ // Deprecated.
ClassDB::bind_method(D_METHOD("property_get_sync", "path"), &SceneReplicationConfig::property_get_sync);
ClassDB::bind_method(D_METHOD("property_set_sync", "path", "enabled"), &SceneReplicationConfig::property_set_sync);
ClassDB::bind_method(D_METHOD("property_get_watch", "path"), &SceneReplicationConfig::property_get_watch);
diff --git a/modules/multiplayer/scene_replication_config.h b/modules/multiplayer/scene_replication_config.h
index 44f8259904..3f870ba2d8 100644
--- a/modules/multiplayer/scene_replication_config.h
+++ b/modules/multiplayer/scene_replication_config.h
@@ -39,12 +39,18 @@ class SceneReplicationConfig : public Resource {
OBJ_SAVE_TYPE(SceneReplicationConfig);
RES_BASE_EXTENSION("repl");
+public:
+ enum ReplicationMode {
+ REPLICATION_MODE_NEVER,
+ REPLICATION_MODE_ALWAYS,
+ REPLICATION_MODE_ON_CHANGE,
+ };
+
private:
struct ReplicationProperty {
NodePath name;
bool spawn = true;
- bool sync = true;
- bool watch = false;
+ ReplicationMode mode = REPLICATION_MODE_ALWAYS;
bool operator==(const ReplicationProperty &p_to) {
return name == p_to.name;
@@ -61,6 +67,9 @@ private:
List<NodePath> spawn_props;
List<NodePath> sync_props;
List<NodePath> watch_props;
+ bool dirty = false;
+
+ void _update();
protected:
static void _bind_methods();
@@ -86,11 +95,16 @@ public:
bool property_get_watch(const NodePath &p_path);
void property_set_watch(const NodePath &p_path, bool p_enabled);
- const List<NodePath> &get_spawn_properties() { return spawn_props; }
- const List<NodePath> &get_sync_properties() { return sync_props; }
- const List<NodePath> &get_watch_properties() { return watch_props; }
+ ReplicationMode property_get_replication_mode(const NodePath &p_path);
+ void property_set_replication_mode(const NodePath &p_path, ReplicationMode p_mode);
+
+ const List<NodePath> &get_spawn_properties();
+ const List<NodePath> &get_sync_properties();
+ const List<NodePath> &get_watch_properties();
SceneReplicationConfig() {}
};
+VARIANT_ENUM_CAST(SceneReplicationConfig::ReplicationMode);
+
#endif // SCENE_REPLICATION_CONFIG_H
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index 7f12fea4bf..e350f2f68b 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -155,7 +155,7 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
- ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(spawner, ERR_INVALID_PARAMETER);
// Track node.
const ObjectID oid = node->get_instance_id();
TrackedNode &tobj = _track(oid);
@@ -226,7 +226,7 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
- ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER);
// Add to synchronizer list.
TrackedNode &tobj = _track(p_obj->get_instance_id());
@@ -270,7 +270,7 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
- ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER);
sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed));
// Untrack synchronizer.
const ObjectID oid = node->get_instance_id();
@@ -291,9 +291,9 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co
void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) {
MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(p_sid);
- ERR_FAIL_COND(!sync); // Bug.
+ ERR_FAIL_NULL(sync); // Bug.
Node *node = sync->get_root_node();
- ERR_FAIL_COND(!node); // Bug.
+ ERR_FAIL_NULL(node); // Bug.
const ObjectID oid = node->get_instance_id();
if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) {
_update_spawn_visibility(p_peer, oid);
@@ -341,7 +341,7 @@ bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer
}
Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) {
- ERR_FAIL_COND_V(!p_sync, ERR_BUG);
+ ERR_FAIL_NULL_V(p_sync, ERR_BUG);
if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) {
return OK;
}
@@ -380,7 +380,7 @@ Error SceneReplicationInterface::_update_sync_visibility(int p_peer, Multiplayer
Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) {
const TrackedNode *tnode = tracked_nodes.getptr(p_oid);
- ERR_FAIL_COND_V(!tnode, ERR_BUG);
+ ERR_FAIL_NULL_V(tnode, ERR_BUG);
MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner);
Node *node = get_id_as<Node>(p_oid);
ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG);
@@ -467,7 +467,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa
const ObjectID oid = p_node->get_instance_id();
const TrackedNode *tnode = tracked_nodes.getptr(oid);
- ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER);
uint32_t nid = tnode->net_id;
ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED);
@@ -549,7 +549,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa
Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) {
const ObjectID oid = p_node->get_instance_id();
const TrackedNode *tnode = tracked_nodes.getptr(oid);
- ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER);
MAKE_ROOM(5);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN;
@@ -568,7 +568,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
uint32_t node_target = decode_uint32(&p_buffer[ofs]);
ofs += 4;
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_path_cache()->get_cached_object(p_from, node_target));
- ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
+ ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST);
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
@@ -592,7 +592,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
// Check that we can spawn.
Node *parent = spawner->get_node_or_null(spawner->get_spawn_path());
- ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(parent, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA);
Node *node = nullptr;
@@ -611,7 +611,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
// Scene based spawn.
node = spawner->instantiate_scene(scene_id);
}
- ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED);
+ ERR_FAIL_NULL_V(node, ERR_UNAUTHORIZED);
node->set_name(name);
// Add and track remote
@@ -656,13 +656,13 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
PeerInfo &pinfo = peers_info[p_from];
ERR_FAIL_COND_V(!pinfo.recv_nodes.has(net_id), ERR_UNAUTHORIZED);
Node *node = get_id_as<Node>(pinfo.recv_nodes[net_id]);
- ERR_FAIL_COND_V(!node, ERR_BUG);
+ ERR_FAIL_NULL_V(node, ERR_BUG);
pinfo.recv_nodes.erase(net_id);
const ObjectID oid = node->get_instance_id();
ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_BUG);
MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tracked_nodes[oid].spawner);
- ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
+ ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST);
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
if (node->get_parent() != nullptr) {
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index da1a044c9d..48e1d13f9c 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -134,7 +134,7 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i
String SceneRPCInterface::get_rpc_md5(const Object *p_obj) {
const Node *node = Object::cast_to<Node>(p_obj);
- ERR_FAIL_COND_V(!node, "");
+ ERR_FAIL_NULL_V(node, "");
const RPCConfigCache cache = _get_node_config(node);
String rpc_list;
for (const KeyValue<uint16_t, RPCConfig> &config : cache.configs) {
@@ -145,7 +145,7 @@ String SceneRPCInterface::get_rpc_md5(const Object *p_obj) {
Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) {
Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
- ERR_FAIL_COND_V(!root_node, nullptr);
+ ERR_FAIL_NULL_V(root_node, nullptr);
Node *node = nullptr;
if (p_node_target & 0x80000000) {
@@ -225,7 +225,7 @@ void SceneRPCInterface::process_rpc(int p_from, const uint8_t *p_packet, int p_p
}
Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len);
- ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found.");
+ ERR_FAIL_NULL_MSG(node, "Invalid packet received. Requested node was not found.");
uint16_t name_id = 0;
switch (name_id_compression) {
diff --git a/modules/navigation/config.py b/modules/navigation/config.py
index d22f9454ed..a42f27fbe1 100644
--- a/modules/navigation/config.py
+++ b/modules/navigation/config.py
@@ -1,5 +1,5 @@
def can_build(env, platform):
- return True
+ return not env["disable_3d"]
def configure(env):
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index 634d70d3bd..18d66c7b69 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -32,12 +32,12 @@
#ifdef TOOLS_ENABLED
-#include "../navigation_mesh_generator.h"
-
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/navigation_region_3d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
@@ -55,8 +55,8 @@ void NavigationMeshEditor::_node_removed(Node *p_node) {
void NavigationMeshEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons")));
- button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
+ button_bake->set_icon(get_theme_icon(SNAME("Bake"), EditorStringName(EditorIcons)));
+ button_reset->set_icon(get_theme_icon(SNAME("Reload"), EditorStringName(EditorIcons)));
} break;
}
}
@@ -64,7 +64,7 @@ void NavigationMeshEditor::_notification(int p_what) {
void NavigationMeshEditor::_bake_pressed() {
button_bake->set_pressed(false);
- ERR_FAIL_COND(!node);
+ ERR_FAIL_NULL(node);
Ref<NavigationMesh> navmesh = node->get_navigation_mesh();
if (!navmesh.is_valid()) {
err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work."));
@@ -99,18 +99,16 @@ void NavigationMeshEditor::_bake_pressed() {
}
}
- NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
- Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
- source_geometry_data.instantiate();
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(node->get_navigation_mesh(), source_geometry_data, node);
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(node->get_navigation_mesh(), source_geometry_data);
+ node->bake_navigation_mesh(false);
node->update_gizmos();
}
void NavigationMeshEditor::_clear_pressed() {
if (node) {
- NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
+ if (node->get_navigation_mesh().is_valid()) {
+ node->get_navigation_mesh()->clear();
+ }
}
button_bake->set_pressed(false);
@@ -136,17 +134,18 @@ NavigationMeshEditor::NavigationMeshEditor() {
bake_hbox = memnew(HBoxContainer);
button_bake = memnew(Button);
- button_bake->set_flat(true);
+ button_bake->set_theme_type_variation("FlatButton");
bake_hbox->add_child(button_bake);
button_bake->set_toggle_mode(true);
- button_bake->set_text(TTR("Bake NavMesh"));
+ button_bake->set_text(TTR("Bake NavigationMesh"));
+ button_bake->set_tooltip_text(TTR("Bakes the NavigationMesh by first parsing the scene for source geometry and then creating the navigation mesh vertices and polygons."));
button_bake->connect("pressed", callable_mp(this, &NavigationMeshEditor::_bake_pressed));
button_reset = memnew(Button);
- button_reset->set_flat(true);
+ button_reset->set_theme_type_variation("FlatButton");
bake_hbox->add_child(button_reset);
- // No button text, we only use a revert icon which is set when entering the tree.
- button_reset->set_tooltip_text(TTR("Clear the navigation mesh."));
+ button_reset->set_text(TTR("Clear NavigationMesh"));
+ button_reset->set_tooltip_text(TTR("Clears the internal NavigationMesh vertices and polygons."));
button_reset->connect("pressed", callable_mp(this, &NavigationMeshEditor::_clear_pressed));
bake_info = memnew(Label);
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index c0fa6eef9e..6a3bf6793e 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -31,8 +31,8 @@
#include "godot_navigation_server.h"
#ifndef _3D_DISABLED
-#include "navigation_mesh_generator.h"
-#endif
+#include "nav_mesh_generator_3d.h"
+#endif // _3D_DISABLED
#include "core/os/mutex.h"
@@ -116,7 +116,7 @@ RID GodotNavigationServer::map_create() {
COMMAND_2(map_set_active, RID, p_map, bool, p_active) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
if (p_active) {
if (!map_is_active(p_map)) {
@@ -133,126 +133,126 @@ COMMAND_2(map_set_active, RID, p_map, bool, p_active) {
bool GodotNavigationServer::map_is_active(RID p_map) const {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, false);
+ ERR_FAIL_NULL_V(map, false);
return active_maps.find(map) >= 0;
}
COMMAND_2(map_set_up, RID, p_map, Vector3, p_up) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_up(p_up);
}
Vector3 GodotNavigationServer::map_get_up(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
return map->get_up();
}
COMMAND_2(map_set_cell_size, RID, p_map, real_t, p_cell_size) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_cell_size(p_cell_size);
}
real_t GodotNavigationServer::map_get_cell_size(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, 0);
+ ERR_FAIL_NULL_V(map, 0);
return map->get_cell_size();
}
COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_cell_height(p_cell_height);
}
real_t GodotNavigationServer::map_get_cell_height(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, 0);
+ ERR_FAIL_NULL_V(map, 0);
return map->get_cell_height();
}
COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_use_edge_connections(p_enabled);
}
bool GodotNavigationServer::map_get_use_edge_connections(RID p_map) const {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, false);
+ ERR_FAIL_NULL_V(map, false);
return map->get_use_edge_connections();
}
COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_edge_connection_margin(p_connection_margin);
}
real_t GodotNavigationServer::map_get_edge_connection_margin(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, 0);
+ ERR_FAIL_NULL_V(map, 0);
return map->get_edge_connection_margin();
}
COMMAND_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
map->set_link_connection_radius(p_connection_radius);
}
real_t GodotNavigationServer::map_get_link_connection_radius(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, 0);
+ ERR_FAIL_NULL_V(map, 0);
return map->get_link_connection_radius();
}
Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, Vector<Vector3>());
+ ERR_FAIL_NULL_V(map, Vector<Vector3>());
return map->get_path(p_origin, p_destination, p_optimize, p_navigation_layers, nullptr, nullptr, nullptr);
}
Vector3 GodotNavigationServer::map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
return map->get_closest_point_to_segment(p_from, p_to, p_use_collision);
}
Vector3 GodotNavigationServer::map_get_closest_point(RID p_map, const Vector3 &p_point) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
return map->get_closest_point(p_point);
}
Vector3 GodotNavigationServer::map_get_closest_point_normal(RID p_map, const Vector3 &p_point) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
return map->get_closest_point_normal(p_point);
}
RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 &p_point) const {
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, RID());
+ ERR_FAIL_NULL_V(map, RID());
return map->get_closest_point_owner(p_point);
}
@@ -260,7 +260,7 @@ RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3
TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const {
TypedArray<RID> link_rids;
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, link_rids);
+ ERR_FAIL_NULL_V(map, link_rids);
const LocalVector<NavLink *> &links = map->get_links();
link_rids.resize(links.size());
@@ -274,7 +274,7 @@ TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const {
TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const {
TypedArray<RID> regions_rids;
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, regions_rids);
+ ERR_FAIL_NULL_V(map, regions_rids);
const LocalVector<NavRegion *> &regions = map->get_regions();
regions_rids.resize(regions.size());
@@ -288,7 +288,7 @@ TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const {
TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
TypedArray<RID> agents_rids;
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, agents_rids);
+ ERR_FAIL_NULL_V(map, agents_rids);
const LocalVector<NavAgent *> &agents = map->get_agents();
agents_rids.resize(agents.size());
@@ -302,7 +302,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const {
TypedArray<RID> obstacles_rids;
const NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND_V(map == nullptr, obstacles_rids);
+ ERR_FAIL_NULL_V(map, obstacles_rids);
const LocalVector<NavObstacle *> obstacles = map->get_obstacles();
obstacles_rids.resize(obstacles.size());
for (uint32_t i = 0; i < obstacles.size(); i++) {
@@ -313,7 +313,7 @@ TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const {
RID GodotNavigationServer::region_get_map(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, RID());
+ ERR_FAIL_NULL_V(region, RID());
if (region->get_map()) {
return region->get_map()->get_self();
@@ -323,7 +323,7 @@ RID GodotNavigationServer::region_get_map(RID p_region) const {
RID GodotNavigationServer::agent_get_map(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND_V(agent == nullptr, RID());
+ ERR_FAIL_NULL_V(agent, RID());
if (agent->get_map()) {
return agent->get_map()->get_self();
@@ -342,35 +342,35 @@ RID GodotNavigationServer::region_create() {
COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_enabled(p_enabled);
}
bool GodotNavigationServer::region_get_enabled(RID p_region) const {
const NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, false);
+ ERR_FAIL_NULL_V(region, false);
return region->get_enabled();
}
COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_use_edge_connections(p_enabled);
}
bool GodotNavigationServer::region_get_use_edge_connections(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, false);
+ ERR_FAIL_NULL_V(region, false);
return region->get_use_edge_connections();
}
COMMAND_2(region_set_map, RID, p_region, RID, p_map) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
NavMap *map = map_owner.get_or_null(p_map);
@@ -379,14 +379,14 @@ COMMAND_2(region_set_map, RID, p_region, RID, p_map) {
COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_transform(p_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_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
ERR_FAIL_COND(p_enter_cost < 0.0);
region->set_enter_cost(p_enter_cost);
@@ -394,14 +394,14 @@ COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) {
real_t GodotNavigationServer::region_get_enter_cost(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, 0);
+ ERR_FAIL_NULL_V(region, 0);
return region->get_enter_cost();
}
COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
ERR_FAIL_COND(p_travel_cost < 0.0);
region->set_travel_cost(p_travel_cost);
@@ -409,28 +409,28 @@ COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) {
real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, 0);
+ ERR_FAIL_NULL_V(region, 0);
return region->get_travel_cost();
}
COMMAND_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_owner_id(p_owner_id);
}
ObjectID GodotNavigationServer::region_get_owner_id(RID p_region) const {
const NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, ObjectID());
+ ERR_FAIL_NULL_V(region, ObjectID());
return region->get_owner_id();
}
bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const {
const NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, false);
+ ERR_FAIL_NULL_V(region, false);
if (region->get_map()) {
RID closest_point_owner = map_get_closest_point_owner(region->get_map()->get_self(), p_point);
@@ -441,21 +441,21 @@ bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_poi
COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_navigation_layers(p_navigation_layers);
}
uint32_t GodotNavigationServer::region_get_navigation_layers(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(region == nullptr, 0);
+ ERR_FAIL_NULL_V(region, 0);
return region->get_navigation_layers();
}
COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh) {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND(region == nullptr);
+ ERR_FAIL_NULL(region);
region->set_mesh(p_navigation_mesh);
}
@@ -463,37 +463,37 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi
#ifndef DISABLE_DEPRECATED
void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) {
ERR_FAIL_COND(p_navigation_mesh.is_null());
- ERR_FAIL_COND(p_root_node == nullptr);
+ ERR_FAIL_NULL(p_root_node);
WARN_PRINT_ONCE("NavigationServer3D::region_bake_navigation_mesh() is deprecated due to core threading changes. To upgrade existing code, first create a NavigationMeshSourceGeometryData3D resource. Use this resource with method parse_source_geometry_data() to parse the SceneTree for nodes that should contribute to the navigation mesh baking. The SceneTree parsing needs to happen on the main thread. After the parsing is finished use the resource with method bake_from_source_geometry_data() to bake a navigation mesh..");
#ifndef _3D_DISABLED
- NavigationMeshGenerator::get_singleton()->clear(p_navigation_mesh);
+ p_navigation_mesh->clear();
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
source_geometry_data.instantiate();
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, source_geometry_data, p_root_node);
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data);
+ parse_source_geometry_data(p_navigation_mesh, source_geometry_data, p_root_node);
+ bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data);
#endif
}
#endif // DISABLE_DEPRECATED
int GodotNavigationServer::region_get_connections_count(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(!region, 0);
+ ERR_FAIL_NULL_V(region, 0);
return region->get_connections_count();
}
Vector3 GodotNavigationServer::region_get_connection_pathway_start(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(!region, Vector3());
+ ERR_FAIL_NULL_V(region, Vector3());
return region->get_connection_pathway_start(p_connection_id);
}
Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
- ERR_FAIL_COND_V(!region, Vector3());
+ ERR_FAIL_NULL_V(region, Vector3());
return region->get_connection_pathway_end(p_connection_id);
}
@@ -509,7 +509,7 @@ RID GodotNavigationServer::link_create() {
COMMAND_2(link_set_map, RID, p_link, RID, p_map) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
NavMap *map = map_owner.get_or_null(p_map);
@@ -518,7 +518,7 @@ COMMAND_2(link_set_map, RID, p_link, RID, p_map) {
RID GodotNavigationServer::link_get_map(const RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, RID());
+ ERR_FAIL_NULL_V(link, RID());
if (link->get_map()) {
return link->get_map()->get_self();
@@ -528,112 +528,112 @@ RID GodotNavigationServer::link_get_map(const RID p_link) const {
COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_enabled(p_enabled);
}
bool GodotNavigationServer::link_get_enabled(RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, false);
+ ERR_FAIL_NULL_V(link, false);
return link->get_enabled();
}
COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_bidirectional(p_bidirectional);
}
bool GodotNavigationServer::link_is_bidirectional(RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, false);
+ ERR_FAIL_NULL_V(link, false);
return link->is_bidirectional();
}
COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_navigation_layers(p_navigation_layers);
}
uint32_t GodotNavigationServer::link_get_navigation_layers(const RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, 0);
+ ERR_FAIL_NULL_V(link, 0);
return link->get_navigation_layers();
}
COMMAND_2(link_set_start_position, RID, p_link, Vector3, p_position) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_start_position(p_position);
}
Vector3 GodotNavigationServer::link_get_start_position(RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, Vector3());
+ ERR_FAIL_NULL_V(link, Vector3());
return link->get_start_position();
}
COMMAND_2(link_set_end_position, RID, p_link, Vector3, p_position) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_end_position(p_position);
}
Vector3 GodotNavigationServer::link_get_end_position(RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, Vector3());
+ ERR_FAIL_NULL_V(link, Vector3());
return link->get_end_position();
}
COMMAND_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_enter_cost(p_enter_cost);
}
real_t GodotNavigationServer::link_get_enter_cost(const RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, 0);
+ ERR_FAIL_NULL_V(link, 0);
return link->get_enter_cost();
}
COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_travel_cost(p_travel_cost);
}
real_t GodotNavigationServer::link_get_travel_cost(const RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, 0);
+ ERR_FAIL_NULL_V(link, 0);
return link->get_travel_cost();
}
COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id) {
NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND(link == nullptr);
+ ERR_FAIL_NULL(link);
link->set_owner_id(p_owner_id);
}
ObjectID GodotNavigationServer::link_get_owner_id(RID p_link) const {
const NavLink *link = link_owner.get_or_null(p_link);
- ERR_FAIL_COND_V(link == nullptr, ObjectID());
+ ERR_FAIL_NULL_V(link, ObjectID());
return link->get_owner_id();
}
@@ -649,35 +649,35 @@ RID GodotNavigationServer::agent_create() {
COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_avoidance_enabled(p_enabled);
}
bool GodotNavigationServer::agent_get_avoidance_enabled(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND_V(agent == nullptr, false);
+ ERR_FAIL_NULL_V(agent, false);
return agent->is_avoidance_enabled();
}
COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_use_3d_avoidance(p_enabled);
}
bool GodotNavigationServer::agent_get_use_3d_avoidance(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND_V(agent == nullptr, false);
+ ERR_FAIL_NULL_V(agent, false);
return agent->get_use_3d_avoidance();
}
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
NavMap *map = map_owner.get_or_null(p_map);
@@ -686,28 +686,28 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
COMMAND_2(agent_set_paused, RID, p_agent, bool, p_paused) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_paused(p_paused);
}
bool GodotNavigationServer::agent_get_paused(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND_V(agent == nullptr, false);
+ ERR_FAIL_NULL_V(agent, false);
return agent->get_paused();
}
COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_neighbor_distance(p_distance);
}
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_max_neighbors(p_count);
}
@@ -715,7 +715,7 @@ COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
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.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_time_horizon_agents(p_time_horizon);
}
@@ -723,7 +723,7 @@ COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) {
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.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_time_horizon_obstacles(p_time_horizon);
}
@@ -731,7 +731,7 @@ COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon
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);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_radius(p_radius);
}
@@ -739,7 +739,7 @@ COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_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);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_height(p_height);
}
@@ -747,42 +747,42 @@ COMMAND_2(agent_set_height, RID, p_agent, real_t, p_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);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_max_speed(p_max_speed);
}
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_velocity(p_velocity);
}
COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_velocity_forced(p_velocity);
}
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_position(p_position);
}
bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND_V(agent == nullptr, false);
+ ERR_FAIL_NULL_V(agent, false);
return agent->is_map_changed();
}
COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) {
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_avoidance_callback(p_callback);
@@ -797,13 +797,13 @@ COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_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_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_avoidance_layers(p_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_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_avoidance_mask(p_mask);
}
@@ -811,7 +811,7 @@ 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.");
NavAgent *agent = agent_owner.get_or_null(p_agent);
- ERR_FAIL_COND(agent == nullptr);
+ ERR_FAIL_NULL(agent);
agent->set_avoidance_priority(p_priority);
}
@@ -833,35 +833,35 @@ RID GodotNavigationServer::obstacle_create() {
COMMAND_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_avoidance_enabled(p_enabled);
}
bool GodotNavigationServer::obstacle_get_avoidance_enabled(RID p_obstacle) const {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND_V(obstacle == nullptr, false);
+ ERR_FAIL_NULL_V(obstacle, false);
return obstacle->is_avoidance_enabled();
}
COMMAND_2(obstacle_set_use_3d_avoidance, RID, p_obstacle, bool, p_enabled) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_use_3d_avoidance(p_enabled);
}
bool GodotNavigationServer::obstacle_get_use_3d_avoidance(RID p_obstacle) const {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND_V(obstacle == nullptr, false);
+ ERR_FAIL_NULL_V(obstacle, false);
return obstacle->get_use_3d_avoidance();
}
COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
NavMap *map = map_owner.get_or_null(p_map);
@@ -870,7 +870,7 @@ COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) {
RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND_V(obstacle == nullptr, RID());
+ ERR_FAIL_NULL_V(obstacle, RID());
if (obstacle->get_map()) {
return obstacle->get_map()->get_self();
}
@@ -879,14 +879,14 @@ RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const {
COMMAND_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_paused(p_paused);
}
bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND_V(obstacle == nullptr, false);
+ ERR_FAIL_NULL_V(obstacle, false);
return obstacle->get_paused();
}
@@ -894,52 +894,72 @@ bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const {
COMMAND_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive.");
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_radius(p_radius);
}
COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_height(p_height);
}
COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_velocity(p_velocity);
}
COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) {
NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle);
- ERR_FAIL_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_position(p_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_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_vertices(p_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_COND(obstacle == nullptr);
+ ERR_FAIL_NULL(obstacle);
obstacle->set_avoidance_layers(p_layers);
}
-void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+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
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
-#endif
+ 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().");
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified.");
+ ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
+#endif // _3D_DISABLED
}
-void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+void GodotNavigationServer::bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
-#endif
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // _3D_DISABLED
+}
+
+void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+#ifndef _3D_DISABLED
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // _3D_DISABLED
}
COMMAND_1(free, RID, p_object) {
@@ -1059,13 +1079,21 @@ void GodotNavigationServer::flush_queries() {
void GodotNavigationServer::map_force_update(RID p_map) {
NavMap *map = map_owner.get_or_null(p_map);
- ERR_FAIL_COND(map == nullptr);
+ ERR_FAIL_NULL(map);
flush_queries();
map->sync();
}
+void GodotNavigationServer::sync() {
+#ifndef _3D_DISABLED
+ if (navmesh_generator_3d) {
+ navmesh_generator_3d->sync();
+ }
+#endif // _3D_DISABLED
+}
+
void GodotNavigationServer::process(real_t p_delta_time) {
flush_queries();
@@ -1117,11 +1145,28 @@ void GodotNavigationServer::process(real_t p_delta_time) {
pm_edge_free_count = _new_pm_edge_free_count;
}
+void GodotNavigationServer::init() {
+#ifndef _3D_DISABLED
+ navmesh_generator_3d = memnew(NavMeshGenerator3D);
+#endif // _3D_DISABLED
+}
+
+void GodotNavigationServer::finish() {
+ flush_queries();
+#ifndef _3D_DISABLED
+ if (navmesh_generator_3d) {
+ navmesh_generator_3d->finish();
+ memdelete(navmesh_generator_3d);
+ navmesh_generator_3d = nullptr;
+ }
+#endif // _3D_DISABLED
+}
+
PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_parameters) const {
PathQueryResult r_query_result;
const NavMap *map = map_owner.get_or_null(p_parameters.map);
- ERR_FAIL_COND_V(map == nullptr, r_query_result);
+ ERR_FAIL_NULL_V(map, r_query_result);
// run the pathfinding
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 0b3789102c..4ead4fc398 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -56,6 +56,9 @@
void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
class GodotNavigationServer;
+#ifndef _3D_DISABLED
+class NavMeshGenerator3D;
+#endif // _3D_DISABLED
struct SetCommand {
virtual ~SetCommand() {}
@@ -79,6 +82,10 @@ class GodotNavigationServer : public NavigationServer3D {
LocalVector<NavMap *> active_maps;
LocalVector<uint32_t> active_maps_update_id;
+#ifndef _3D_DISABLED
+ NavMeshGenerator3D *navmesh_generator_3d = nullptr;
+#endif // _3D_DISABLED
+
// Performance Monitor
int pm_region_count = 0;
int pm_agent_count = 0;
@@ -225,8 +232,9 @@ public:
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override;
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers);
- virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
- virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) 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;
+ virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
COMMAND_1(free, RID, p_object);
@@ -234,6 +242,9 @@ public:
void flush_queries();
virtual void process(real_t p_delta_time) override;
+ virtual void init() override;
+ virtual void sync() override;
+ virtual void finish() override;
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override;
diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp
new file mode 100644
index 0000000000..b54729e06f
--- /dev/null
+++ b/modules/navigation/godot_navigation_server_2d.cpp
@@ -0,0 +1,369 @@
+/**************************************************************************/
+/* godot_navigation_server_2d.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 "godot_navigation_server_2d.h"
+
+#ifdef CLIPPER2_ENABLED
+#include "nav_mesh_generator_2d.h"
+#endif // CLIPPER2_ENABLED
+
+#include "servers/navigation_server_3d.h"
+
+#define FORWARD_0(FUNC_NAME) \
+ GodotNavigationServer2D::FUNC_NAME() { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(); \
+ }
+
+#define FORWARD_0_C(FUNC_NAME) \
+ GodotNavigationServer2D::FUNC_NAME() \
+ const { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(); \
+ }
+
+#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0) { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
+ }
+
+#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \
+ const { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
+ }
+
+#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \
+ const { \
+ return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \
+ }
+
+#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
+ }
+
+#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \
+ const { \
+ return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
+ }
+
+#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \
+ const { \
+ return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
+ }
+
+#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \
+ GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \
+ const { \
+ return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \
+ }
+
+static RID rid_to_rid(const RID d) {
+ return d;
+}
+
+static bool bool_to_bool(const bool d) {
+ return d;
+}
+
+static int int_to_int(const int d) {
+ return d;
+}
+
+static uint32_t uint32_to_uint32(const uint32_t d) {
+ return d;
+}
+
+static real_t real_to_real(const real_t d) {
+ return d;
+}
+
+static Vector3 v2_to_v3(const Vector2 d) {
+ return Vector3(d.x, 0.0, d.y);
+}
+
+static Vector2 v3_to_v2(const Vector3 &d) {
+ return Vector2(d.x, d.z);
+}
+
+static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) {
+ Vector<Vector3> nd;
+ nd.resize(d.size());
+ for (int i(0); i < nd.size(); i++) {
+ nd.write[i] = v2_to_v3(d[i]);
+ }
+ return nd;
+}
+
+static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) {
+ Vector<Vector2> nd;
+ nd.resize(d.size());
+ for (int i(0); i < nd.size(); i++) {
+ nd.write[i] = v3_to_v2(d[i]);
+ }
+ return nd;
+}
+
+static Transform3D trf2_to_trf3(const Transform2D &d) {
+ Vector3 o(v2_to_v3(d.get_origin()));
+ Basis b;
+ b.rotate(Vector3(0, -1, 0), d.get_rotation());
+ b.scale(v2_to_v3(d.get_scale()));
+ return Transform3D(b, o);
+}
+
+static ObjectID id_to_id(const ObjectID &id) {
+ return id;
+}
+
+static Callable callable_to_callable(const Callable &c) {
+ return c;
+}
+
+static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
+ if (d.is_valid()) {
+ return d->get_navigation_mesh();
+ } else {
+ return Ref<NavigationMesh>();
+ }
+}
+
+void GodotNavigationServer2D::init() {
+#ifdef CLIPPER2_ENABLED
+ navmesh_generator_2d = memnew(NavMeshGenerator2D);
+ ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D.");
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::sync() {
+#ifdef CLIPPER2_ENABLED
+ if (navmesh_generator_2d) {
+ navmesh_generator_2d->sync();
+ }
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::finish() {
+#ifdef CLIPPER2_ENABLED
+ if (navmesh_generator_2d) {
+ navmesh_generator_2d->finish();
+ memdelete(navmesh_generator_2d);
+ navmesh_generator_2d = nullptr;
+ }
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+ 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().");
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon.");
+ ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified.");
+ ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
+
+#ifdef CLIPPER2_ENABLED
+ ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+ NavMeshGenerator2D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D.");
+
+#ifdef CLIPPER2_ENABLED
+ ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+ NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D.");
+
+#ifdef CLIPPER2_ENABLED
+ ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+ NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+GodotNavigationServer2D::GodotNavigationServer2D() {}
+
+GodotNavigationServer2D::~GodotNavigationServer2D() {}
+
+TypedArray<RID> FORWARD_0_C(get_maps);
+
+TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid);
+
+RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid);
+
+RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid);
+
+RID FORWARD_0(map_create);
+
+void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool);
+
+bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid);
+
+void GodotNavigationServer2D::map_force_update(RID p_map) {
+ NavigationServer3D::get_singleton()->map_force_update(p_map);
+}
+
+void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid);
+
+Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32);
+
+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);
+
+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);
+bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid);
+
+void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id);
+ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid);
+bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
+
+void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid);
+void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
+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);
+
+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));
+}
+
+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);
+
+RID FORWARD_0(link_create);
+
+void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid);
+RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
+uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
+Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
+Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id);
+ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid);
+
+RID GodotNavigationServer2D::agent_create() {
+ RID agent = NavigationServer3D::get_singleton()->agent_create();
+ return agent;
+}
+
+void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool);
+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);
+void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int);
+void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real);
+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);
+void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3);
+
+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);
+
+void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32);
+void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real);
+
+RID GodotNavigationServer2D::obstacle_create() {
+ RID obstacle = NavigationServer3D::get_singleton()->obstacle_create();
+ return obstacle;
+}
+void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid);
+void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid);
+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);
+void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3);
+void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+
+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));
+}
+
+void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const {
+ ERR_FAIL_COND(!p_query_parameters.is_valid());
+ ERR_FAIL_COND(!p_query_result.is_valid());
+
+ const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters());
+
+ p_query_result->set_path(vector_v3_to_v2(_query_result.path));
+ p_query_result->set_path_types(_query_result.path_types);
+ p_query_result->set_path_rids(_query_result.path_rids);
+ p_query_result->set_path_owner_ids(_query_result.path_owner_ids);
+}
diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h
new file mode 100644
index 0000000000..337f5f40d8
--- /dev/null
+++ b/modules/navigation/godot_navigation_server_2d.h
@@ -0,0 +1,234 @@
+/**************************************************************************/
+/* godot_navigation_server_2d.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 GODOT_NAVIGATION_SERVER_2D_H
+#define GODOT_NAVIGATION_SERVER_2D_H
+
+#include "nav_agent.h"
+#include "nav_link.h"
+#include "nav_map.h"
+#include "nav_obstacle.h"
+#include "nav_region.h"
+
+#include "servers/navigation_server_2d.h"
+
+#ifdef CLIPPER2_ENABLED
+class NavMeshGenerator2D;
+#endif // CLIPPER2_ENABLED
+
+// This server exposes the `NavigationServer3D` features in the 2D world.
+class GodotNavigationServer2D : public NavigationServer2D {
+ GDCLASS(GodotNavigationServer2D, NavigationServer2D);
+
+#ifdef CLIPPER2_ENABLED
+ NavMeshGenerator2D *navmesh_generator_2d = nullptr;
+#endif // CLIPPER2_ENABLED
+
+public:
+ GodotNavigationServer2D();
+ virtual ~GodotNavigationServer2D();
+
+ virtual TypedArray<RID> get_maps() const override;
+
+ virtual RID map_create() override;
+ virtual void map_set_active(RID p_map, bool p_active) override;
+ virtual bool map_is_active(RID p_map) const override;
+ virtual void map_set_cell_size(RID p_map, real_t p_cell_size) override;
+ virtual real_t map_get_cell_size(RID p_map) const override;
+ virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) override;
+ virtual bool map_get_use_edge_connections(RID p_map) const override;
+ virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override;
+ virtual real_t map_get_edge_connection_margin(RID p_map) const override;
+ virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override;
+ virtual real_t map_get_link_connection_radius(RID p_map) const override;
+ virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override;
+ virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override;
+ virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override;
+ virtual TypedArray<RID> map_get_links(RID p_map) const override;
+ virtual TypedArray<RID> map_get_regions(RID p_map) const override;
+ 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 RID region_create() override;
+ virtual void region_set_enabled(RID p_region, bool p_enabled) override;
+ virtual bool region_get_enabled(RID p_region) const override;
+ virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) override;
+ virtual bool region_get_use_edge_connections(RID p_region) const override;
+ virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) override;
+ virtual real_t region_get_enter_cost(RID p_region) const override;
+ virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) override;
+ virtual real_t region_get_travel_cost(RID p_region) const override;
+ virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) override;
+ virtual ObjectID region_get_owner_id(RID p_region) const override;
+ virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const override;
+ virtual void region_set_map(RID p_region, RID p_map) override;
+ virtual RID region_get_map(RID p_region) const override;
+ 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 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 RID link_create() override;
+
+ /// Set the map of this link.
+ virtual void link_set_map(RID p_link, RID p_map) override;
+ virtual RID link_get_map(RID p_link) const override;
+
+ virtual void link_set_enabled(RID p_link, bool p_enabled) override;
+ virtual bool link_get_enabled(RID p_link) const override;
+
+ /// Set whether this link travels in both directions.
+ virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) override;
+ virtual bool link_is_bidirectional(RID p_link) const override;
+
+ /// Set the link's layers.
+ virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override;
+ virtual uint32_t link_get_navigation_layers(RID p_link) const override;
+
+ /// Set the start position of the link.
+ virtual void link_set_start_position(RID p_link, Vector2 p_position) override;
+ virtual Vector2 link_get_start_position(RID p_link) const override;
+
+ /// Set the end position of the link.
+ virtual void link_set_end_position(RID p_link, Vector2 p_position) override;
+ virtual Vector2 link_get_end_position(RID p_link) const override;
+
+ /// Set the enter cost of the link.
+ virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) override;
+ virtual real_t link_get_enter_cost(RID p_link) const override;
+
+ /// Set the travel cost of the link.
+ virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) override;
+ virtual real_t link_get_travel_cost(RID p_link) const override;
+
+ /// Set the node which manages this link.
+ virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) override;
+ virtual ObjectID link_get_owner_id(RID p_link) const override;
+
+ /// Creates the agent.
+ virtual RID agent_create() override;
+
+ /// Put the agent in the map.
+ virtual void agent_set_map(RID p_agent, RID p_map) override;
+ virtual RID agent_get_map(RID p_agent) const override;
+
+ virtual void agent_set_paused(RID p_agent, bool p_paused) override;
+ virtual bool agent_get_paused(RID p_agent) const override;
+
+ virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override;
+ virtual bool agent_get_avoidance_enabled(RID p_agent) const override;
+
+ /// The maximum distance (center point to
+ /// center point) to other agents this agent
+ /// takes into account in the navigation. The
+ /// larger this number, the longer the running
+ /// time of the simulation. If the number is too
+ /// 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;
+
+ /// The maximum number of other agents this
+ /// agent takes into account in the navigation.
+ /// The larger this number, the longer the
+ /// running time of the simulation. If the
+ /// number is too low, the simulation will not
+ /// be safe.
+ virtual void agent_set_max_neighbors(RID p_agent, int p_count) override;
+
+ /// The minimal amount of time for which this
+ /// agent's velocities that are computed by the
+ /// simulation are safe with respect to other
+ /// agents. The larger this number, the sooner
+ /// this agent will respond to the presence of
+ /// 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 void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override;
+
+ /// The radius of this agent.
+ /// Must be non-negative.
+ virtual void agent_set_radius(RID p_agent, real_t p_radius) 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;
+
+ /// 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;
+
+ /// 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;
+
+ /// Position of the agent in world space.
+ virtual void agent_set_position(RID p_agent, Vector2 p_position) 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 void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override;
+ virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override;
+ virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override;
+
+ virtual RID obstacle_create() override;
+ virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override;
+ virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const override;
+ virtual void obstacle_set_map(RID p_obstacle, RID p_map) override;
+ virtual RID obstacle_get_map(RID p_obstacle) const override;
+ 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 void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override;
+ virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override;
+ virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override;
+ virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override;
+
+ virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override;
+
+ virtual void init() override;
+ virtual void sync() override;
+ virtual void finish() override;
+ virtual void free(RID p_object) override;
+
+ virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+};
+
+#endif // GODOT_NAVIGATION_SERVER_2D_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 745c227fe5..737ccaf3cd 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -431,6 +431,17 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
if (p_optimize) {
// Set the apex poly/point to the end point
gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
+
+ Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end };
+ const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway);
+ if (end_point.is_equal_approx(back_edge_closest_point)) {
+ // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing.
+ // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon.
+ if (apex_poly->back_navigation_poly_id != -1) {
+ apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id];
+ }
+ }
+
Vector3 apex_point = end_point;
gd::NavigationPoly *left_poly = apex_poly;
@@ -1049,7 +1060,8 @@ void NavMap::sync() {
}
// Update the update ID.
- map_update_id = (map_update_id + 1) % 9999999;
+ // Some code treats 0 as a failure case, so we avoid returning 0.
+ map_update_id = map_update_id % 9999999 + 1;
}
// Do we have modified obstacle positions?
diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp
new file mode 100644
index 0000000000..089744c8bd
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_2d.cpp
@@ -0,0 +1,830 @@
+/**************************************************************************/
+/* nav_mesh_generator_2d.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 "nav_mesh_generator_2d.h"
+
+#include "core/config/project_settings.h"
+#include "scene/2d/mesh_instance_2d.h"
+#include "scene/2d/multimesh_instance_2d.h"
+#include "scene/2d/physics_body_2d.h"
+#include "scene/2d/polygon_2d.h"
+#include "scene/2d/tile_map.h"
+#include "scene/resources/capsule_shape_2d.h"
+#include "scene/resources/circle_shape_2d.h"
+#include "scene/resources/concave_polygon_shape_2d.h"
+#include "scene/resources/convex_polygon_shape_2d.h"
+#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
+#include "scene/resources/navigation_polygon.h"
+#include "scene/resources/rectangle_shape_2d.h"
+
+#include "thirdparty/clipper2/include/clipper2/clipper.h"
+#include "thirdparty/misc/polypartition.h"
+
+NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr;
+Mutex NavMeshGenerator2D::baking_navmesh_mutex;
+Mutex NavMeshGenerator2D::generator_task_mutex;
+bool NavMeshGenerator2D::use_threads = true;
+bool NavMeshGenerator2D::baking_use_multiple_threads = true;
+bool NavMeshGenerator2D::baking_use_high_priority_threads = true;
+HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes;
+HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks;
+
+NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() {
+ return singleton;
+}
+
+NavMeshGenerator2D::NavMeshGenerator2D() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = this;
+
+ baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
+ baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
+
+ // Using threads might cause problems on certain exports or with the Editor on certain devices.
+ // This is the main switch to turn threaded navmesh baking off should the need arise.
+ use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
+}
+
+NavMeshGenerator2D::~NavMeshGenerator2D() {
+ cleanup();
+}
+
+void NavMeshGenerator2D::sync() {
+ if (generator_tasks.size() == 0) {
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
+
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
+
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
+ }
+ }
+
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator2D::cleanup() {
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ baking_navmeshes.clear();
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator2D::finish() {
+ cleanup();
+}
+
+void NavMeshGenerator2D::parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+ ERR_FAIL_COND(!Thread::is_main_thread());
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_NULL(p_root_node);
+ ERR_FAIL_COND(!p_root_node->is_inside_tree());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node);
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
+
+ baking_navmesh_mutex.lock();
+ baking_navmeshes.erase(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ if (!use_threads) {
+ bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_task_mutex.lock();
+ NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D);
+ generator_task->navigation_mesh = p_navigation_mesh;
+ generator_task->source_geometry_data = p_source_geometry_data;
+ generator_task->callback = p_callback;
+ generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
+ generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D");
+ generator_tasks.insert(generator_task->thread_task_id, generator_task);
+ generator_task_mutex.unlock();
+}
+
+void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
+ NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg);
+
+ generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
+
+ generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED;
+}
+
+void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
+ generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node);
+
+ if (p_recurse_children) {
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children);
+ }
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ MeshInstance2D *mesh_instance = Object::cast_to<MeshInstance2D>(p_node);
+
+ if (mesh_instance == nullptr) {
+ return;
+ }
+
+ NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+ return;
+ }
+
+ Ref<Mesh> mesh = mesh_instance->get_mesh();
+ if (!mesh.is_valid()) {
+ return;
+ }
+
+ const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform();
+
+ using namespace Clipper2Lib;
+
+ Paths64 subject_paths, dummy_clip_paths;
+
+ for (int i = 0; i < mesh->get_surface_count(); i++) {
+ if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
+ continue;
+ }
+
+ if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) {
+ continue;
+ }
+
+ Path64 subject_path;
+
+ int index_count = 0;
+ if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+ index_count = mesh->surface_get_array_index_len(i);
+ } else {
+ index_count = mesh->surface_get_array_len(i);
+ }
+
+ ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
+
+ Array a = mesh->surface_get_arrays(i);
+
+ Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX];
+
+ if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+ Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
+ for (int vertex_index : mesh_indices) {
+ const Vector2 &vertex = mesh_vertices[vertex_index];
+ const Point64 &point = Point64(vertex.x, vertex.y);
+ subject_path.push_back(point);
+ }
+ } else {
+ for (const Vector2 &vertex : mesh_vertices) {
+ const Point64 &point = Point64(vertex.x, vertex.y);
+ subject_path.push_back(point);
+ }
+ }
+ subject_paths.push_back(subject_path);
+ }
+
+ Paths64 path_solution;
+
+ path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero);
+
+ //path_solution = RamerDouglasPeucker(path_solution, 0.025);
+
+ Vector<Vector<Vector2>> polypaths;
+
+ for (const Path64 &scaled_path : path_solution) {
+ Vector<Vector2> shape_outline;
+ for (const Point64 &scaled_point : scaled_path) {
+ shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
+ }
+
+ for (int i = 0; i < shape_outline.size(); i++) {
+ shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]);
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ MultiMeshInstance2D *multimesh_instance = Object::cast_to<MultiMeshInstance2D>(p_node);
+
+ if (multimesh_instance == nullptr) {
+ return;
+ }
+
+ NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+ return;
+ }
+
+ Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
+ if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) {
+ return;
+ }
+
+ Ref<Mesh> mesh = multimesh->get_mesh();
+ if (!mesh.is_valid()) {
+ return;
+ }
+
+ using namespace Clipper2Lib;
+
+ Paths64 mesh_subject_paths, dummy_clip_paths;
+
+ for (int i = 0; i < mesh->get_surface_count(); i++) {
+ if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
+ continue;
+ }
+
+ if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) {
+ continue;
+ }
+
+ Path64 subject_path;
+
+ int index_count = 0;
+ if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+ index_count = mesh->surface_get_array_index_len(i);
+ } else {
+ index_count = mesh->surface_get_array_len(i);
+ }
+
+ ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
+
+ Array a = mesh->surface_get_arrays(i);
+
+ Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX];
+
+ if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+ Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
+ for (int vertex_index : mesh_indices) {
+ const Vector2 &vertex = mesh_vertices[vertex_index];
+ const Point64 &point = Point64(vertex.x, vertex.y);
+ subject_path.push_back(point);
+ }
+ } else {
+ for (const Vector2 &vertex : mesh_vertices) {
+ const Point64 &point = Point64(vertex.x, vertex.y);
+ subject_path.push_back(point);
+ }
+ }
+ mesh_subject_paths.push_back(subject_path);
+ }
+
+ Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero);
+
+ //path_solution = RamerDouglasPeucker(path_solution, 0.025);
+
+ int multimesh_instance_count = multimesh->get_visible_instance_count();
+ if (multimesh_instance_count == -1) {
+ multimesh_instance_count = multimesh->get_instance_count();
+ }
+
+ const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform();
+
+ for (int i = 0; i < multimesh_instance_count; i++) {
+ const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i);
+
+ for (const Path64 &mesh_path : mesh_path_solution) {
+ Vector<Vector2> shape_outline;
+
+ for (const Point64 &mesh_path_point : mesh_path) {
+ shape_outline.push_back(Point2(static_cast<real_t>(mesh_path_point.x), static_cast<real_t>(mesh_path_point.y)));
+ }
+
+ for (int j = 0; j < shape_outline.size(); j++) {
+ shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]);
+ }
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ Polygon2D *polygon_2d = Object::cast_to<Polygon2D>(p_node);
+
+ if (polygon_2d == nullptr) {
+ return;
+ }
+
+ NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) {
+ const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform();
+
+ Vector<Vector2> shape_outline = polygon_2d->get_polygon();
+ for (int i = 0; i < shape_outline.size(); i++) {
+ shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]);
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ StaticBody2D *static_body = Object::cast_to<StaticBody2D>(p_node);
+
+ if (static_body == nullptr) {
+ return;
+ }
+
+ NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+ return;
+ }
+
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();
+ if (!(static_body->get_collision_layer() & parsed_collision_mask)) {
+ return;
+ }
+
+ List<uint32_t> shape_owners;
+ static_body->get_shape_owners(&shape_owners);
+
+ for (uint32_t shape_owner : shape_owners) {
+ if (static_body->is_shape_owner_disabled(shape_owner)) {
+ continue;
+ }
+
+ const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
+
+ for (int shape_index = 0; shape_index < shape_count; shape_index++) {
+ Ref<Shape2D> s = static_body->shape_owner_get_shape(shape_owner, shape_index);
+
+ if (s.is_null()) {
+ continue;
+ }
+
+ const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
+
+ RectangleShape2D *rectangle_shape = Object::cast_to<RectangleShape2D>(*s);
+ if (rectangle_shape) {
+ Vector<Vector2> shape_outline;
+
+ const Vector2 &rectangle_size = rectangle_shape->get_size();
+
+ shape_outline.resize(5);
+ shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5);
+ shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5);
+ shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5);
+ shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5);
+ shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5);
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+
+ CapsuleShape2D *capsule_shape = Object::cast_to<CapsuleShape2D>(*s);
+ if (capsule_shape) {
+ const real_t capsule_height = capsule_shape->get_height();
+ const real_t capsule_radius = capsule_shape->get_radius();
+
+ Vector<Vector2> shape_outline;
+ const real_t turn_step = Math_TAU / 12.0;
+ shape_outline.resize(14);
+ int shape_outline_inx = 0;
+ for (int i = 0; i < 12; i++) {
+ Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius);
+
+ shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs);
+ shape_outline_inx += 1;
+ if (i == 3 || i == 9) {
+ shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs);
+ shape_outline_inx += 1;
+ }
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+
+ CircleShape2D *circle_shape = Object::cast_to<CircleShape2D>(*s);
+ if (circle_shape) {
+ const real_t circle_radius = circle_shape->get_radius();
+
+ Vector<Vector2> shape_outline;
+ int circle_edge_count = 12;
+ shape_outline.resize(circle_edge_count);
+
+ const real_t turn_step = Math_TAU / real_t(circle_edge_count);
+ for (int i = 0; i < circle_edge_count; i++) {
+ shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius);
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+
+ ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to<ConcavePolygonShape2D>(*s);
+ if (concave_polygon_shape) {
+ Vector<Vector2> shape_outline = concave_polygon_shape->get_segments();
+
+ for (int i = 0; i < shape_outline.size(); i++) {
+ shape_outline.write[i] = static_body_xform.xform(shape_outline[i]);
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+
+ ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to<ConvexPolygonShape2D>(*s);
+ if (convex_polygon_shape) {
+ Vector<Vector2> shape_outline = convex_polygon_shape->get_points();
+
+ for (int i = 0; i < shape_outline.size(); i++) {
+ shape_outline.write[i] = static_body_xform.xform(shape_outline[i]);
+ }
+
+ p_source_geometry_data->add_obstruction_outline(shape_outline);
+ }
+ }
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ TileMap *tilemap = Object::cast_to<TileMap>(p_node);
+
+ if (tilemap == nullptr) {
+ return;
+ }
+
+ NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();
+
+ if (tilemap->get_layers_count() <= 0) {
+ return;
+ }
+
+ int tilemap_layer = 0; // only main tile map layer is supported
+
+ Ref<TileSet> tile_set = tilemap->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int physics_layers_count = tile_set->get_physics_layers_count();
+ int navigation_layers_count = tile_set->get_navigation_layers_count();
+
+ if (physics_layers_count <= 0 && navigation_layers_count <= 0) {
+ return;
+ }
+
+ const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform();
+ TypedArray<Vector2i> used_cells = tilemap->get_used_cells(tilemap_layer);
+
+ for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) {
+ const Vector2i &cell = used_cells[used_cell_index];
+
+ const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false);
+
+ 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);
+ if (navigation_polygon.is_valid()) {
+ for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) {
+ Vector<Vector2> traversable_outline = navigation_polygon->get_outline(outline_index);
+
+ for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) {
+ traversable_outline.write[traversable_outline_index] = tile_transform_offset.xform(traversable_outline[traversable_outline_index]);
+ }
+
+ p_source_geometry_data->_add_traversable_outline(traversable_outline);
+ }
+ }
+ }
+
+ 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++) {
+ Vector<Vector2> obstruction_outline = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index);
+
+ for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) {
+ obstruction_outline.write[obstruction_outline_index] = tile_transform_offset.xform(obstruction_outline[obstruction_outline_index]);
+ }
+
+ p_source_geometry_data->_add_obstruction_outline(obstruction_outline);
+ }
+ }
+ }
+}
+
+void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) {
+ List<Node *> parse_nodes;
+
+ if (p_navigation_mesh->get_source_geometry_mode() == NavigationPolygon::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
+ parse_nodes.push_back(p_root_node);
+ } else {
+ p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_geometry_group_name(), &parse_nodes);
+ }
+
+ Transform2D root_node_transform = Transform2D();
+ if (Object::cast_to<Node2D>(p_root_node)) {
+ root_node_transform = Object::cast_to<Node2D>(p_root_node)->get_global_transform().affine_inverse();
+ }
+
+ p_source_geometry_data->clear();
+ p_source_geometry_data->root_node_transform = root_node_transform;
+
+ bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
+
+ for (Node *E : parse_nodes) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children);
+ }
+};
+
+static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) {
+ using namespace Clipper2Lib;
+
+ Vector<Vector2> polygon_vertices;
+
+ for (const Point64 &polypath_point : p_polypath_item->Polygon()) {
+ polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y)));
+ }
+
+ TPPLPoly tp;
+ tp.Init(polygon_vertices.size());
+ for (int j = 0; j < polygon_vertices.size(); j++) {
+ tp[j] = polygon_vertices[j];
+ }
+
+ if (p_polypath_item->IsHole()) {
+ tp.SetOrientation(TPPL_ORIENTATION_CW);
+ tp.SetHole(true);
+ } else {
+ tp.SetOrientation(TPPL_ORIENTATION_CCW);
+ }
+ p_tppl_in_polygon.push_back(tp);
+
+ for (size_t i = 0; i < p_polypath_item->Count(); i++) {
+ const PolyPath64 *polypath_item = p_polypath_item->Child(i);
+ generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item);
+ }
+}
+
+bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) {
+ ERR_FAIL_COND_V(!p_callback.is_valid(), false);
+
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(nullptr, 0, result, ce);
+
+ return ce.error == Callable::CallError::CALL_OK;
+}
+
+void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) {
+ if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) {
+ return;
+ }
+
+ if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+ return;
+ }
+
+ int outline_count = p_navigation_mesh->get_outline_count();
+ const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines();
+ const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines();
+
+ if (outline_count == 0 && traversable_outlines.size() == 0) {
+ return;
+ }
+
+ using namespace Clipper2Lib;
+
+ Paths64 traversable_polygon_paths;
+ Paths64 obstruction_polygon_paths;
+
+ for (int i = 0; i < outline_count; i++) {
+ const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
+ Path64 subject_path;
+ for (const Vector2 &traversable_point : traversable_outline) {
+ const Point64 &point = Point64(traversable_point.x, traversable_point.y);
+ subject_path.push_back(point);
+ }
+ traversable_polygon_paths.push_back(subject_path);
+ }
+
+ for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
+ Path64 subject_path;
+ for (const Vector2 &traversable_point : traversable_outline) {
+ const Point64 &point = Point64(traversable_point.x, traversable_point.y);
+ subject_path.push_back(point);
+ }
+ traversable_polygon_paths.push_back(subject_path);
+ }
+
+ for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
+ Path64 clip_path;
+ for (const Vector2 &obstruction_point : obstruction_outline) {
+ const Point64 &point = Point64(obstruction_point.x, obstruction_point.y);
+ clip_path.push_back(point);
+ }
+ obstruction_polygon_paths.push_back(clip_path);
+ }
+
+ Paths64 path_solution;
+
+ // first merge all traversable polygons according to user specified fill rule
+ Paths64 dummy_clip_path;
+ traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero);
+ // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry
+ obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero);
+
+ path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero);
+
+ real_t agent_radius_offset = p_navigation_mesh->get_agent_radius();
+ if (agent_radius_offset > 0.0) {
+ path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon);
+ }
+ //path_solution = RamerDouglasPeucker(path_solution, 0.025); //
+
+ Vector<Vector<Vector2>> new_baked_outlines;
+
+ for (const Path64 &scaled_path : path_solution) {
+ Vector<Vector2> polypath;
+ for (const Point64 &scaled_point : scaled_path) {
+ polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
+ }
+ new_baked_outlines.push_back(polypath);
+ }
+
+ if (new_baked_outlines.size() == 0) {
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+
+ Paths64 polygon_paths;
+
+ for (const Vector<Vector2> &baked_outline : new_baked_outlines) {
+ Path64 polygon_path;
+ for (const Vector2 &baked_outline_point : baked_outline) {
+ const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y);
+ polygon_path.push_back(point);
+ }
+ polygon_paths.push_back(polygon_path);
+ }
+
+ ClipType clipper_cliptype = ClipType::Union;
+
+ List<TPPLPoly> tppl_in_polygon, tppl_out_polygon;
+
+ PolyTree64 polytree;
+ Clipper64 clipper_64;
+
+ clipper_64.AddSubject(polygon_paths);
+ clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree);
+
+ for (size_t i = 0; i < polytree.Count(); i++) {
+ const PolyPath64 *polypath_item = polytree[i];
+ generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item);
+ }
+
+ TPPLPartition tpart;
+ if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
+ ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+
+ Vector<Vector2> new_vertices;
+ Vector<Vector<int>> new_polygons;
+
+ HashMap<Vector2, int> points;
+ for (List<TPPLPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) {
+ TPPLPoly &tp = I->get();
+
+ Vector<int> new_polygon;
+
+ for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
+ HashMap<Vector2, int>::Iterator E = points.find(tp[i]);
+ if (!E) {
+ E = points.insert(tp[i], new_vertices.size());
+ new_vertices.push_back(tp[i]);
+ }
+ new_polygon.push_back(E->value);
+ }
+
+ new_polygons.push_back(new_polygon);
+ }
+
+ p_navigation_mesh->set_vertices(new_vertices);
+ p_navigation_mesh->clear_polygons();
+ for (int i = 0; i < new_polygons.size(); i++) {
+ p_navigation_mesh->add_polygon(new_polygons[i]);
+ }
+}
diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h
new file mode 100644
index 0000000000..763ad24636
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_2d.h
@@ -0,0 +1,100 @@
+/**************************************************************************/
+/* nav_mesh_generator_2d.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 NAV_MESH_GENERATOR_2D_H
+#define NAV_MESH_GENERATOR_2D_H
+
+#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
+
+class Node;
+class NavigationPolygon;
+class NavigationMeshSourceGeometryData2D;
+
+class NavMeshGenerator2D : public Object {
+ static NavMeshGenerator2D *singleton;
+
+ static Mutex baking_navmesh_mutex;
+ static Mutex generator_task_mutex;
+
+ static bool use_threads;
+ static bool baking_use_multiple_threads;
+ static bool baking_use_high_priority_threads;
+
+ struct NavMeshGeneratorTask2D {
+ enum TaskStatus {
+ BAKING_STARTED,
+ BAKING_FINISHED,
+ BAKING_FAILED,
+ CALLBACK_DISPATCHED,
+ CALLBACK_FAILED,
+ };
+
+ Ref<NavigationPolygon> navigation_mesh;
+ Ref<NavigationMeshSourceGeometryData2D> source_geometry_data;
+ Callable callback;
+ WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
+ NavMeshGeneratorTask2D::TaskStatus status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
+ };
+
+ static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> generator_tasks;
+
+ static void generator_thread_bake(void *p_arg);
+
+ static HashSet<Ref<NavigationPolygon>> baking_navmeshes;
+
+ static void generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children);
+ static void generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node);
+ static void generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data);
+
+ static void generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+
+ static bool generator_emit_callback(const Callable &p_callback);
+
+public:
+ static NavMeshGenerator2D *get_singleton();
+
+ static void sync();
+ static void cleanup();
+ static void finish();
+
+ static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+
+ NavMeshGenerator2D();
+ ~NavMeshGenerator2D();
+};
+
+#endif // NAV_MESH_GENERATOR_2D_H
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
new file mode 100644
index 0000000000..5de1c4cba9
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -0,0 +1,838 @@
+/**************************************************************************/
+/* nav_mesh_generator_3d.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. */
+/**************************************************************************/
+
+#ifndef _3D_DISABLED
+
+#include "nav_mesh_generator_3d.h"
+
+#include "core/config/project_settings.h"
+#include "core/math/convex_hull.h"
+#include "core/os/thread.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/multimesh_instance_3d.h"
+#include "scene/3d/physics_body_3d.h"
+#include "scene/resources/box_shape_3d.h"
+#include "scene/resources/capsule_shape_3d.h"
+#include "scene/resources/concave_polygon_shape_3d.h"
+#include "scene/resources/convex_polygon_shape_3d.h"
+#include "scene/resources/cylinder_shape_3d.h"
+#include "scene/resources/height_map_shape_3d.h"
+#include "scene/resources/navigation_mesh.h"
+#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
+#include "scene/resources/primitive_meshes.h"
+#include "scene/resources/shape_3d.h"
+#include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/world_boundary_shape_3d.h"
+
+#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+
+#ifdef MODULE_CSG_ENABLED
+#include "modules/csg/csg_shape.h"
+#endif
+#ifdef MODULE_GRIDMAP_ENABLED
+#include "modules/gridmap/grid_map.h"
+#endif
+
+#include <Recast.h>
+
+NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
+Mutex NavMeshGenerator3D::baking_navmesh_mutex;
+Mutex NavMeshGenerator3D::generator_task_mutex;
+bool NavMeshGenerator3D::use_threads = true;
+bool NavMeshGenerator3D::baking_use_multiple_threads = true;
+bool NavMeshGenerator3D::baking_use_high_priority_threads = true;
+HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes;
+HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks;
+
+NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
+ return singleton;
+}
+
+NavMeshGenerator3D::NavMeshGenerator3D() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = this;
+
+ baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
+ baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
+
+ // Using threads might cause problems on certain exports or with the Editor on certain devices.
+ // This is the main switch to turn threaded navmesh baking off should the need arise.
+ use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
+}
+
+NavMeshGenerator3D::~NavMeshGenerator3D() {
+ cleanup();
+}
+
+void NavMeshGenerator3D::sync() {
+ if (generator_tasks.size() == 0) {
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
+
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
+
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
+ }
+ }
+
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator3D::cleanup() {
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ baking_navmeshes.clear();
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator3D::finish() {
+ cleanup();
+}
+
+void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+ ERR_FAIL_COND(!Thread::is_main_thread());
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_NULL(p_root_node);
+ ERR_FAIL_COND(!p_root_node->is_inside_tree());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node);
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
+
+ baking_navmesh_mutex.lock();
+ baking_navmeshes.erase(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ if (!use_threads) {
+ bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
+ return;
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_task_mutex.lock();
+ NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
+ generator_task->navigation_mesh = p_navigation_mesh;
+ generator_task->source_geometry_data = p_source_geometry_data;
+ generator_task->callback = p_callback;
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
+ generator_tasks.insert(generator_task->thread_task_id, generator_task);
+ generator_task_mutex.unlock();
+}
+
+void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
+ NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
+
+ generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
+
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED;
+}
+
+void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
+ generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_staticbody3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#ifdef MODULE_CSG_ENABLED
+ generator_parse_csgshape3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#endif
+#ifdef MODULE_GRIDMAP_ENABLED
+ generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#endif
+
+ if (p_recurse_children) {
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children);
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_meshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node);
+
+ if (mesh_instance) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Ref<Mesh> mesh = mesh_instance->get_mesh();
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, mesh_instance->get_global_transform());
+ }
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_multimeshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ MultiMeshInstance3D *multimesh_instance = Object::cast_to<MultiMeshInstance3D>(p_node);
+
+ if (multimesh_instance) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
+ if (multimesh.is_valid()) {
+ Ref<Mesh> mesh = multimesh->get_mesh();
+ if (mesh.is_valid()) {
+ int n = multimesh->get_visible_instance_count();
+ if (n == -1) {
+ n = multimesh->get_instance_count();
+ }
+ for (int i = 0; i < n; i++) {
+ p_source_geometry_data->add_mesh(mesh, multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i));
+ }
+ }
+ }
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ StaticBody3D *static_body = Object::cast_to<StaticBody3D>(p_node);
+
+ if (static_body) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (static_body->get_collision_layer() & parsed_collision_mask)) {
+ List<uint32_t> shape_owners;
+ static_body->get_shape_owners(&shape_owners);
+ for (uint32_t shape_owner : shape_owners) {
+ if (static_body->is_shape_owner_disabled(shape_owner)) {
+ continue;
+ }
+ const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
+ for (int shape_index = 0; shape_index < shape_count; shape_index++) {
+ Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, shape_index);
+ if (s.is_null()) {
+ continue;
+ }
+
+ const Transform3D transform = static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
+
+ BoxShape3D *box = Object::cast_to<BoxShape3D>(*s);
+ if (box) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ BoxMesh::create_mesh_array(arr, box->get_size());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ CapsuleShape3D *capsule = Object::cast_to<CapsuleShape3D>(*s);
+ if (capsule) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ CylinderShape3D *cylinder = Object::cast_to<CylinderShape3D>(*s);
+ if (cylinder) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ SphereShape3D *sphere = Object::cast_to<SphereShape3D>(*s);
+ if (sphere) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ ConcavePolygonShape3D *concave_polygon = Object::cast_to<ConcavePolygonShape3D>(*s);
+ if (concave_polygon) {
+ p_source_geometry_data->add_faces(concave_polygon->get_faces(), transform);
+ }
+
+ ConvexPolygonShape3D *convex_polygon = Object::cast_to<ConvexPolygonShape3D>(*s);
+ if (convex_polygon) {
+ Vector<Vector3> varr = Variant(convex_polygon->get_points());
+ Geometry3D::MeshData md;
+
+ Error err = ConvexHullComputer::convex_hull(varr, md);
+
+ if (err == OK) {
+ PackedVector3Array faces;
+
+ for (const Geometry3D::MeshData::Face &face : md.faces) {
+ for (uint32_t k = 2; k < face.indices.size(); ++k) {
+ faces.push_back(md.vertices[face.indices[0]]);
+ faces.push_back(md.vertices[face.indices[k - 1]]);
+ faces.push_back(md.vertices[face.indices[k]]);
+ }
+ }
+
+ p_source_geometry_data->add_faces(faces, transform);
+ }
+ }
+
+ HeightMapShape3D *heightmap_shape = Object::cast_to<HeightMapShape3D>(*s);
+ if (heightmap_shape) {
+ int heightmap_depth = heightmap_shape->get_map_depth();
+ int heightmap_width = heightmap_shape->get_map_width();
+
+ if (heightmap_depth >= 2 && heightmap_width >= 2) {
+ const Vector<real_t> &map_data = heightmap_shape->get_map_data();
+
+ Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
+ Vector2 start = heightmap_gridsize * -0.5;
+
+ Vector<Vector3> vertex_array;
+ vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
+ int map_data_current_index = 0;
+
+ for (int d = 0; d < heightmap_depth; d++) {
+ for (int w = 0; w < heightmap_width; w++) {
+ if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
+ float top_left_height = map_data[map_data_current_index];
+ float top_right_height = map_data[map_data_current_index + 1];
+ float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
+ float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
+
+ Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
+ Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
+ Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
+ Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
+
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_left);
+ vertex_array.push_back(top_left);
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_right);
+ vertex_array.push_back(bottom_left);
+ }
+ map_data_current_index += 1;
+ }
+ }
+ if (vertex_array.size() > 0) {
+ p_source_geometry_data->add_faces(vertex_array, transform);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#ifdef MODULE_CSG_ENABLED
+void NavMeshGenerator3D::generator_parse_csgshape3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ CSGShape3D *csgshape3d = Object::cast_to<CSGShape3D>(p_node);
+
+ if (csgshape3d) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_node);
+ Array meshes = csg_shape->get_meshes();
+ if (!meshes.is_empty()) {
+ Ref<Mesh> mesh = meshes[1];
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, csg_shape->get_global_transform());
+ }
+ }
+ }
+ }
+}
+#endif // MODULE_CSG_ENABLED
+
+#ifdef MODULE_GRIDMAP_ENABLED
+void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ GridMap *gridmap = Object::cast_to<GridMap>(p_node);
+
+ if (gridmap) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Array meshes = gridmap->get_meshes();
+ Transform3D xform = gridmap->get_global_transform();
+ for (int i = 0; i < meshes.size(); i += 2) {
+ Ref<Mesh> mesh = meshes[i + 1];
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, xform * (Transform3D)meshes[i]);
+ }
+ }
+ }
+
+ else if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (gridmap->get_collision_layer() & parsed_collision_mask)) {
+ Array shapes = gridmap->get_collision_shapes();
+ for (int i = 0; i < shapes.size(); i += 2) {
+ RID shape = shapes[i + 1];
+ PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape);
+ Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape);
+
+ switch (type) {
+ case PhysicsServer3D::SHAPE_SPHERE: {
+ real_t radius = data;
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ SphereMesh::create_mesh_array(arr, radius, radius * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_BOX: {
+ Vector3 extents = data;
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ BoxMesh::create_mesh_array(arr, extents * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CAPSULE: {
+ Dictionary dict = data;
+ real_t radius = dict["radius"];
+ real_t height = dict["height"];
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CapsuleMesh::create_mesh_array(arr, radius, height);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CYLINDER: {
+ Dictionary dict = data;
+ real_t radius = dict["radius"];
+ real_t height = dict["height"];
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CylinderMesh::create_mesh_array(arr, radius, radius, height);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CONVEX_POLYGON: {
+ PackedVector3Array vertices = data;
+ Geometry3D::MeshData md;
+
+ Error err = ConvexHullComputer::convex_hull(vertices, md);
+
+ if (err == OK) {
+ PackedVector3Array faces;
+
+ for (const Geometry3D::MeshData::Face &face : md.faces) {
+ for (uint32_t k = 2; k < face.indices.size(); ++k) {
+ faces.push_back(md.vertices[face.indices[0]]);
+ faces.push_back(md.vertices[face.indices[k - 1]]);
+ faces.push_back(md.vertices[face.indices[k]]);
+ }
+ }
+
+ p_source_geometry_data->add_faces(faces, shapes[i]);
+ }
+ } break;
+ case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: {
+ Dictionary dict = data;
+ PackedVector3Array faces = Variant(dict["faces"]);
+ p_source_geometry_data->add_faces(faces, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_HEIGHTMAP: {
+ Dictionary dict = data;
+ ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights"
+ int heightmap_depth = dict["depth"];
+ int heightmap_width = dict["width"];
+
+ if (heightmap_depth >= 2 && heightmap_width >= 2) {
+ const Vector<real_t> &map_data = dict["heights"];
+
+ Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
+ Vector2 start = heightmap_gridsize * -0.5;
+
+ Vector<Vector3> vertex_array;
+ vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
+ int map_data_current_index = 0;
+
+ for (int d = 0; d < heightmap_depth; d++) {
+ for (int w = 0; w < heightmap_width; w++) {
+ if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
+ float top_left_height = map_data[map_data_current_index];
+ float top_right_height = map_data[map_data_current_index + 1];
+ float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
+ float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
+
+ Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
+ Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
+ Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
+ Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
+
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_left);
+ vertex_array.push_back(top_left);
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_right);
+ vertex_array.push_back(bottom_left);
+ }
+ map_data_current_index += 1;
+ }
+ }
+ if (vertex_array.size() > 0) {
+ p_source_geometry_data->add_faces(vertex_array, shapes[i]);
+ }
+ }
+ } break;
+ default: {
+ WARN_PRINT("Unsupported collision shape type.");
+ } break;
+ }
+ }
+ }
+ }
+}
+#endif // MODULE_GRIDMAP_ENABLED
+
+void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) {
+ List<Node *> parse_nodes;
+
+ if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
+ parse_nodes.push_back(p_root_node);
+ } else {
+ p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes);
+ }
+
+ Transform3D root_node_transform = Transform3D();
+ if (Object::cast_to<Node3D>(p_root_node)) {
+ root_node_transform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse();
+ }
+
+ p_source_geometry_data->clear();
+ p_source_geometry_data->root_node_transform = root_node_transform;
+
+ bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
+
+ for (Node *parse_node : parse_nodes) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, parse_node, recurse_children);
+ }
+};
+
+void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data) {
+ if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) {
+ return;
+ }
+
+ const Vector<float> &vertices = p_source_geometry_data->get_vertices();
+ const Vector<int> &indices = p_source_geometry_data->get_indices();
+
+ if (vertices.size() < 3 || indices.size() < 3) {
+ return;
+ }
+
+ rcHeightfield *hf = nullptr;
+ rcCompactHeightfield *chf = nullptr;
+ rcContourSet *cset = nullptr;
+ rcPolyMesh *poly_mesh = nullptr;
+ rcPolyMeshDetail *detail_mesh = nullptr;
+ rcContext ctx;
+
+ // added to keep track of steps, no functionality right now
+ String bake_state = "";
+
+ bake_state = "Setting up Configuration..."; // step #1
+
+ const float *verts = vertices.ptr();
+ const int nverts = vertices.size() / 3;
+ const int *tris = indices.ptr();
+ const int ntris = indices.size() / 3;
+
+ float bmin[3], bmax[3];
+ rcCalcBounds(verts, nverts, bmin, bmax);
+
+ rcConfig cfg;
+ memset(&cfg, 0, sizeof(cfg));
+
+ cfg.cs = p_navigation_mesh->get_cell_size();
+ cfg.ch = p_navigation_mesh->get_cell_height();
+ cfg.walkableSlopeAngle = p_navigation_mesh->get_agent_max_slope();
+ cfg.walkableHeight = (int)Math::ceil(p_navigation_mesh->get_agent_height() / cfg.ch);
+ cfg.walkableClimb = (int)Math::floor(p_navigation_mesh->get_agent_max_climb() / cfg.ch);
+ cfg.walkableRadius = (int)Math::ceil(p_navigation_mesh->get_agent_radius() / cfg.cs);
+ cfg.maxEdgeLen = (int)(p_navigation_mesh->get_edge_max_length() / p_navigation_mesh->get_cell_size());
+ cfg.maxSimplificationError = p_navigation_mesh->get_edge_max_error();
+ cfg.minRegionArea = (int)(p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size());
+ cfg.mergeRegionArea = (int)(p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size());
+ cfg.maxVertsPerPoly = (int)p_navigation_mesh->get_vertices_per_polygon();
+ cfg.detailSampleDist = MAX(p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance(), 0.1f);
+ cfg.detailSampleMaxError = p_navigation_mesh->get_cell_height() * p_navigation_mesh->get_detail_sample_max_error();
+
+ if (!Math::is_equal_approx((float)cfg.walkableHeight * cfg.ch, p_navigation_mesh->get_agent_height())) {
+ WARN_PRINT("Property agent_height is ceiled to cell_height voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.walkableClimb * cfg.ch, p_navigation_mesh->get_agent_max_climb())) {
+ WARN_PRINT("Property agent_max_climb is floored to cell_height voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.walkableRadius * cfg.cs, p_navigation_mesh->get_agent_radius())) {
+ WARN_PRINT("Property agent_radius is ceiled to cell_size voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.maxEdgeLen * cfg.cs, p_navigation_mesh->get_edge_max_length())) {
+ WARN_PRINT("Property edge_max_length is rounded to cell_size voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.minRegionArea, p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size())) {
+ WARN_PRINT("Property region_min_size is converted to int and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.mergeRegionArea, p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size())) {
+ WARN_PRINT("Property region_merge_size is converted to int and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.maxVertsPerPoly, p_navigation_mesh->get_vertices_per_polygon())) {
+ WARN_PRINT("Property vertices_per_polygon is converted to int and loses precision.");
+ }
+ if (p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance() < 0.1f) {
+ WARN_PRINT("Property detail_sample_distance is clamped to 0.1 world units as the resulting value from multiplying with cell_size is too low.");
+ }
+
+ cfg.bmin[0] = bmin[0];
+ cfg.bmin[1] = bmin[1];
+ cfg.bmin[2] = bmin[2];
+ cfg.bmax[0] = bmax[0];
+ cfg.bmax[1] = bmax[1];
+ cfg.bmax[2] = bmax[2];
+
+ AABB baking_aabb = p_navigation_mesh->get_filter_baking_aabb();
+ if (baking_aabb.has_volume()) {
+ Vector3 baking_aabb_offset = p_navigation_mesh->get_filter_baking_aabb_offset();
+ cfg.bmin[0] = baking_aabb.position[0] + baking_aabb_offset.x;
+ cfg.bmin[1] = baking_aabb.position[1] + baking_aabb_offset.y;
+ cfg.bmin[2] = baking_aabb.position[2] + baking_aabb_offset.z;
+ cfg.bmax[0] = cfg.bmin[0] + baking_aabb.size[0];
+ cfg.bmax[1] = cfg.bmin[1] + baking_aabb.size[1];
+ cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2];
+ }
+
+ bake_state = "Calculating grid size..."; // step #2
+ rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
+
+ // ~30000000 seems to be around sweetspot where Editor baking breaks
+ if ((cfg.width * cfg.height) > 30000000) {
+ WARN_PRINT("NavigationMesh baking process will likely fail."
+ "\nSource geometry is suspiciously big for the current Cell Size and Cell Height in the NavMesh Resource bake settings."
+ "\nIf baking does not fail, the resulting NavigationMesh will create serious pathfinding performance issues."
+ "\nIt is advised to increase Cell Size and/or Cell Height in the NavMesh Resource bake settings or reduce the size / scale of the source geometry.");
+ }
+
+ bake_state = "Creating heightfield..."; // step #3
+ hf = rcAllocHeightfield();
+
+ ERR_FAIL_NULL(hf);
+ ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
+
+ bake_state = "Marking walkable triangles..."; // step #4
+ {
+ Vector<unsigned char> tri_areas;
+ tri_areas.resize(ntris);
+
+ ERR_FAIL_COND(tri_areas.size() == 0);
+
+ memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
+ rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
+
+ ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
+ }
+
+ if (p_navigation_mesh->get_filter_low_hanging_obstacles()) {
+ rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
+ }
+ if (p_navigation_mesh->get_filter_ledge_spans()) {
+ rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
+ }
+ if (p_navigation_mesh->get_filter_walkable_low_height_spans()) {
+ rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
+ }
+
+ bake_state = "Constructing compact heightfield..."; // step #5
+
+ chf = rcAllocCompactHeightfield();
+
+ ERR_FAIL_NULL(chf);
+ ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
+
+ rcFreeHeightField(hf);
+ hf = nullptr;
+
+ bake_state = "Eroding walkable area..."; // step #6
+
+ ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
+
+ bake_state = "Partitioning..."; // step #7
+
+ if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
+ ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
+ ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ } else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
+ ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ } else {
+ ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
+ }
+
+ bake_state = "Creating contours..."; // step #8
+
+ cset = rcAllocContourSet();
+
+ ERR_FAIL_NULL(cset);
+ ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
+
+ bake_state = "Creating polymesh..."; // step #9
+
+ poly_mesh = rcAllocPolyMesh();
+ ERR_FAIL_NULL(poly_mesh);
+ ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
+
+ detail_mesh = rcAllocPolyMeshDetail();
+ ERR_FAIL_NULL(detail_mesh);
+ ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
+
+ rcFreeCompactHeightfield(chf);
+ chf = nullptr;
+ rcFreeContourSet(cset);
+ cset = nullptr;
+
+ bake_state = "Converting to native navigation mesh..."; // step #10
+
+ Vector<Vector3> nav_vertices;
+
+ for (int i = 0; i < detail_mesh->nverts; i++) {
+ const float *v = &detail_mesh->verts[i * 3];
+ nav_vertices.push_back(Vector3(v[0], v[1], v[2]));
+ }
+ p_navigation_mesh->set_vertices(nav_vertices);
+ p_navigation_mesh->clear_polygons();
+
+ for (int i = 0; i < detail_mesh->nmeshes; i++) {
+ const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4];
+ const unsigned int detail_mesh_bverts = detail_mesh_m[0];
+ const unsigned int detail_mesh_m_btris = detail_mesh_m[2];
+ const unsigned int detail_mesh_ntris = detail_mesh_m[3];
+ const unsigned char *detail_mesh_tris = &detail_mesh->tris[detail_mesh_m_btris * 4];
+ for (unsigned int j = 0; j < detail_mesh_ntris; j++) {
+ Vector<int> nav_indices;
+ nav_indices.resize(3);
+ // Polygon order in recast is opposite than godot's
+ nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0]));
+ nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2]));
+ nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1]));
+ p_navigation_mesh->add_polygon(nav_indices);
+ }
+ }
+
+ bake_state = "Cleanup..."; // step #11
+
+ rcFreePolyMesh(poly_mesh);
+ poly_mesh = nullptr;
+ rcFreePolyMeshDetail(detail_mesh);
+ detail_mesh = nullptr;
+
+ bake_state = "Baking finished."; // step #12
+}
+
+bool NavMeshGenerator3D::generator_emit_callback(const Callable &p_callback) {
+ ERR_FAIL_COND_V(!p_callback.is_valid(), false);
+
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(nullptr, 0, result, ce);
+
+ return ce.error == Callable::CallError::CALL_OK;
+}
+
+#endif // _3D_DISABLED
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
new file mode 100644
index 0000000000..4220927641
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -0,0 +1,109 @@
+/**************************************************************************/
+/* nav_mesh_generator_3d.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 NAV_MESH_GENERATOR_3D_H
+#define NAV_MESH_GENERATOR_3D_H
+
+#ifndef _3D_DISABLED
+
+#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
+#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+
+class Node;
+class NavigationMesh;
+class NavigationMeshSourceGeometryData3D;
+
+class NavMeshGenerator3D : public Object {
+ static NavMeshGenerator3D *singleton;
+
+ static Mutex baking_navmesh_mutex;
+ static Mutex generator_task_mutex;
+
+ static bool use_threads;
+ static bool baking_use_multiple_threads;
+ static bool baking_use_high_priority_threads;
+
+ struct NavMeshGeneratorTask3D {
+ enum TaskStatus {
+ BAKING_STARTED,
+ BAKING_FINISHED,
+ BAKING_FAILED,
+ CALLBACK_DISPATCHED,
+ CALLBACK_FAILED,
+ };
+
+ Ref<NavigationMesh> navigation_mesh;
+ Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
+ Callable callback;
+ WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
+ NavMeshGeneratorTask3D::TaskStatus status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ };
+
+ static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> generator_tasks;
+
+ static void generator_thread_bake(void *p_arg);
+
+ static HashSet<Ref<NavigationMesh>> baking_navmeshes;
+
+ static void generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children);
+ static void generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node);
+ static void generator_bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data);
+
+ static void generator_parse_meshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_multimeshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_staticbody3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#ifdef MODULE_CSG_ENABLED
+ static void generator_parse_csgshape3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#endif // MODULE_CSG_ENABLED
+#ifdef MODULE_GRIDMAP_ENABLED
+ static void generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#endif // MODULE_GRIDMAP_ENABLED
+
+ static bool generator_emit_callback(const Callable &p_callback);
+
+public:
+ static NavMeshGenerator3D *get_singleton();
+
+ static void sync();
+ static void cleanup();
+ static void finish();
+
+ static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+
+ NavMeshGenerator3D();
+ ~NavMeshGenerator3D();
+};
+
+#endif // _3D_DISABLED
+
+#endif // NAV_MESH_GENERATOR_3D_H
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 4e7964ed76..3675aae518 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -89,13 +89,13 @@ int NavRegion::get_connections_count() const {
}
Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const {
- ERR_FAIL_COND_V(!map, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
return connections[p_connection_id].pathway_start;
}
Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const {
- ERR_FAIL_COND_V(!map, Vector3());
+ ERR_FAIL_NULL_V(map, Vector3());
ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
return connections[p_connection_id].pathway_end;
}
diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp
index 89afb4a8ea..8393896db1 100644
--- a/modules/navigation/navigation_mesh_generator.cpp
+++ b/modules/navigation/navigation_mesh_generator.cpp
@@ -32,451 +32,11 @@
#include "navigation_mesh_generator.h"
-#include "core/math/convex_hull.h"
-#include "core/os/thread.h"
-#include "scene/3d/mesh_instance_3d.h"
-#include "scene/3d/multimesh_instance_3d.h"
-#include "scene/3d/physics_body_3d.h"
-#include "scene/resources/box_shape_3d.h"
-#include "scene/resources/capsule_shape_3d.h"
-#include "scene/resources/concave_polygon_shape_3d.h"
-#include "scene/resources/convex_polygon_shape_3d.h"
-#include "scene/resources/cylinder_shape_3d.h"
-#include "scene/resources/height_map_shape_3d.h"
#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
-#include "scene/resources/primitive_meshes.h"
-#include "scene/resources/shape_3d.h"
-#include "scene/resources/sphere_shape_3d.h"
-#include "scene/resources/world_boundary_shape_3d.h"
-
-#ifdef TOOLS_ENABLED
-#include "editor/editor_node.h"
-#endif
-
-#include "modules/modules_enabled.gen.h" // For csg, gridmap.
-
-#ifdef MODULE_CSG_ENABLED
-#include "modules/csg/csg_shape.h"
-#endif
-#ifdef MODULE_GRIDMAP_ENABLED
-#include "modules/gridmap/grid_map.h"
-#endif
+#include "servers/navigation_server_3d.h"
NavigationMeshGenerator *NavigationMeshGenerator::singleton = nullptr;
-void NavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices) {
- p_vertices.push_back(p_vec3.x);
- p_vertices.push_back(p_vec3.y);
- p_vertices.push_back(p_vec3.z);
-}
-
-void NavigationMeshGenerator::_add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- int current_vertex_count;
-
- for (int i = 0; i < p_mesh->get_surface_count(); i++) {
- current_vertex_count = p_vertices.size() / 3;
-
- if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
- continue;
- }
-
- int index_count = 0;
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
- index_count = p_mesh->surface_get_array_index_len(i);
- } else {
- index_count = p_mesh->surface_get_array_len(i);
- }
-
- ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
-
- int face_count = index_count / 3;
-
- Array a = p_mesh->surface_get_arrays(i);
- ERR_CONTINUE(a.is_empty() || (a.size() != Mesh::ARRAY_MAX));
-
- Vector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
- ERR_CONTINUE(mesh_vertices.is_empty());
- const Vector3 *vr = mesh_vertices.ptr();
-
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
- Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
- ERR_CONTINUE(mesh_indices.is_empty() || (mesh_indices.size() != index_count));
- const int *ir = mesh_indices.ptr();
-
- for (int j = 0; j < mesh_vertices.size(); j++) {
- _add_vertex(p_xform.xform(vr[j]), p_vertices);
- }
-
- for (int j = 0; j < face_count; j++) {
- // CCW
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
- }
- } else {
- ERR_CONTINUE(mesh_vertices.size() != index_count);
- face_count = mesh_vertices.size() / 3;
- for (int j = 0; j < face_count; j++) {
- _add_vertex(p_xform.xform(vr[j * 3 + 0]), p_vertices);
- _add_vertex(p_xform.xform(vr[j * 3 + 2]), p_vertices);
- _add_vertex(p_xform.xform(vr[j * 3 + 1]), p_vertices);
-
- p_indices.push_back(current_vertex_count + (j * 3 + 0));
- p_indices.push_back(current_vertex_count + (j * 3 + 1));
- p_indices.push_back(current_vertex_count + (j * 3 + 2));
- }
- }
- }
-}
-
-void NavigationMeshGenerator::_add_mesh_array(const Array &p_array, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- ERR_FAIL_COND(p_array.size() != Mesh::ARRAY_MAX);
-
- Vector<Vector3> mesh_vertices = p_array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND(mesh_vertices.is_empty());
- const Vector3 *vr = mesh_vertices.ptr();
-
- Vector<int> mesh_indices = p_array[Mesh::ARRAY_INDEX];
- ERR_FAIL_COND(mesh_indices.is_empty());
- const int *ir = mesh_indices.ptr();
-
- const int face_count = mesh_indices.size() / 3;
- const int current_vertex_count = p_vertices.size() / 3;
-
- for (int j = 0; j < mesh_vertices.size(); j++) {
- _add_vertex(p_xform.xform(vr[j]), p_vertices);
- }
-
- for (int j = 0; j < face_count; j++) {
- // CCW
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
- }
-}
-
-void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- ERR_FAIL_COND(p_faces.is_empty());
- ERR_FAIL_COND(p_faces.size() % 3 != 0);
- int face_count = p_faces.size() / 3;
- int current_vertex_count = p_vertices.size() / 3;
-
- for (int j = 0; j < face_count; j++) {
- _add_vertex(p_xform.xform(p_faces[j * 3 + 0]), p_vertices);
- _add_vertex(p_xform.xform(p_faces[j * 3 + 1]), p_vertices);
- _add_vertex(p_xform.xform(p_faces[j * 3 + 2]), p_vertices);
-
- p_indices.push_back(current_vertex_count + (j * 3 + 0));
- p_indices.push_back(current_vertex_count + (j * 3 + 2));
- p_indices.push_back(current_vertex_count + (j * 3 + 1));
- }
-}
-
-void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) {
- if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node);
- Ref<Mesh> mesh = mesh_instance->get_mesh();
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices);
- }
- }
-
- if (Object::cast_to<MultiMeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- MultiMeshInstance3D *multimesh_instance = Object::cast_to<MultiMeshInstance3D>(p_node);
- Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
- if (multimesh.is_valid()) {
- Ref<Mesh> mesh = multimesh->get_mesh();
- if (mesh.is_valid()) {
- int n = multimesh->get_visible_instance_count();
- if (n == -1) {
- n = multimesh->get_instance_count();
- }
- for (int i = 0; i < n; i++) {
- _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices);
- }
- }
- }
- }
-
-#ifdef MODULE_CSG_ENABLED
- if (Object::cast_to<CSGShape3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_node);
- Array meshes = csg_shape->get_meshes();
- if (!meshes.is_empty()) {
- Ref<Mesh> mesh = meshes[1];
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices);
- }
- }
- }
-#endif
-
- if (Object::cast_to<StaticBody3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES) {
- StaticBody3D *static_body = Object::cast_to<StaticBody3D>(p_node);
-
- if (static_body->get_collision_layer() & p_collision_mask) {
- List<uint32_t> shape_owners;
- static_body->get_shape_owners(&shape_owners);
- for (uint32_t shape_owner : shape_owners) {
- if (static_body->is_shape_owner_disabled(shape_owner)) {
- continue;
- }
- const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
- for (int i = 0; i < shape_count; i++) {
- Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, i);
- if (s.is_null()) {
- continue;
- }
-
- const Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
-
- BoxShape3D *box = Object::cast_to<BoxShape3D>(*s);
- if (box) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- BoxMesh::create_mesh_array(arr, box->get_size());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- CapsuleShape3D *capsule = Object::cast_to<CapsuleShape3D>(*s);
- if (capsule) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- CylinderShape3D *cylinder = Object::cast_to<CylinderShape3D>(*s);
- if (cylinder) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- SphereShape3D *sphere = Object::cast_to<SphereShape3D>(*s);
- if (sphere) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0);
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- ConcavePolygonShape3D *concave_polygon = Object::cast_to<ConcavePolygonShape3D>(*s);
- if (concave_polygon) {
- _add_faces(concave_polygon->get_faces(), transform, p_vertices, p_indices);
- }
-
- ConvexPolygonShape3D *convex_polygon = Object::cast_to<ConvexPolygonShape3D>(*s);
- if (convex_polygon) {
- Vector<Vector3> varr = Variant(convex_polygon->get_points());
- Geometry3D::MeshData md;
-
- Error err = ConvexHullComputer::convex_hull(varr, md);
-
- if (err == OK) {
- PackedVector3Array faces;
-
- for (const Geometry3D::MeshData::Face &face : md.faces) {
- for (uint32_t k = 2; k < face.indices.size(); ++k) {
- faces.push_back(md.vertices[face.indices[0]]);
- faces.push_back(md.vertices[face.indices[k - 1]]);
- faces.push_back(md.vertices[face.indices[k]]);
- }
- }
-
- _add_faces(faces, transform, p_vertices, p_indices);
- }
- }
-
- HeightMapShape3D *heightmap_shape = Object::cast_to<HeightMapShape3D>(*s);
- if (heightmap_shape) {
- int heightmap_depth = heightmap_shape->get_map_depth();
- int heightmap_width = heightmap_shape->get_map_width();
-
- if (heightmap_depth >= 2 && heightmap_width >= 2) {
- const Vector<real_t> &map_data = heightmap_shape->get_map_data();
-
- Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
- Vector2 start = heightmap_gridsize * -0.5;
-
- Vector<Vector3> vertex_array;
- vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
- int map_data_current_index = 0;
-
- for (int d = 0; d < heightmap_depth; d++) {
- for (int w = 0; w < heightmap_width; w++) {
- if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
- float top_left_height = map_data[map_data_current_index];
- float top_right_height = map_data[map_data_current_index + 1];
- float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
- float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
-
- Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
- Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
- Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
- Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
-
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_left);
- vertex_array.push_back(top_left);
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_right);
- vertex_array.push_back(bottom_left);
- }
- map_data_current_index += 1;
- }
- }
- if (vertex_array.size() > 0) {
- _add_faces(vertex_array, transform, p_vertices, p_indices);
- }
- }
- }
- }
- }
- }
- }
-
-#ifdef MODULE_GRIDMAP_ENABLED
- GridMap *gridmap = Object::cast_to<GridMap>(p_node);
-
- if (gridmap) {
- if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- Array meshes = gridmap->get_meshes();
- Transform3D xform = gridmap->get_global_transform();
- for (int i = 0; i < meshes.size(); i += 2) {
- Ref<Mesh> mesh = meshes[i + 1];
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices);
- }
- }
- }
-
- if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES && (gridmap->get_collision_layer() & p_collision_mask)) {
- Array shapes = gridmap->get_collision_shapes();
- for (int i = 0; i < shapes.size(); i += 2) {
- RID shape = shapes[i + 1];
- PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape);
- Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape);
-
- switch (type) {
- case PhysicsServer3D::SHAPE_SPHERE: {
- real_t radius = data;
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- SphereMesh::create_mesh_array(arr, radius, radius * 2.0);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_BOX: {
- Vector3 extents = data;
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- BoxMesh::create_mesh_array(arr, extents * 2.0);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CAPSULE: {
- Dictionary dict = data;
- real_t radius = dict["radius"];
- real_t height = dict["height"];
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CapsuleMesh::create_mesh_array(arr, radius, height);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CYLINDER: {
- Dictionary dict = data;
- real_t radius = dict["radius"];
- real_t height = dict["height"];
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CylinderMesh::create_mesh_array(arr, radius, radius, height);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CONVEX_POLYGON: {
- PackedVector3Array vertices = data;
- Geometry3D::MeshData md;
-
- Error err = ConvexHullComputer::convex_hull(vertices, md);
-
- if (err == OK) {
- PackedVector3Array faces;
-
- for (const Geometry3D::MeshData::Face &face : md.faces) {
- for (uint32_t k = 2; k < face.indices.size(); ++k) {
- faces.push_back(md.vertices[face.indices[0]]);
- faces.push_back(md.vertices[face.indices[k - 1]]);
- faces.push_back(md.vertices[face.indices[k]]);
- }
- }
-
- _add_faces(faces, shapes[i], p_vertices, p_indices);
- }
- } break;
- case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: {
- Dictionary dict = data;
- PackedVector3Array faces = Variant(dict["faces"]);
- _add_faces(faces, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_HEIGHTMAP: {
- Dictionary dict = data;
- ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights"
- int heightmap_depth = dict["depth"];
- int heightmap_width = dict["width"];
-
- if (heightmap_depth >= 2 && heightmap_width >= 2) {
- const Vector<real_t> &map_data = dict["heights"];
-
- Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
- Vector2 start = heightmap_gridsize * -0.5;
-
- Vector<Vector3> vertex_array;
- vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
- int map_data_current_index = 0;
-
- for (int d = 0; d < heightmap_depth; d++) {
- for (int w = 0; w < heightmap_width; w++) {
- if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
- float top_left_height = map_data[map_data_current_index];
- float top_right_height = map_data[map_data_current_index + 1];
- float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
- float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
-
- Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
- Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
- Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
- Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
-
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_left);
- vertex_array.push_back(top_left);
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_right);
- vertex_array.push_back(bottom_left);
- }
- map_data_current_index += 1;
- }
- }
- if (vertex_array.size() > 0) {
- _add_faces(vertex_array, shapes[i], p_vertices, p_indices);
- }
- }
- } break;
- default: {
- WARN_PRINT("Unsupported collision shape type.");
- } break;
- }
- }
- }
- }
-#endif
-
- if (p_recurse_children) {
- for (int i = 0; i < p_node->get_child_count(); i++) {
- _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children);
- }
- }
-}
-
NavigationMeshGenerator *NavigationMeshGenerator::get_singleton() {
return singleton;
}
@@ -500,285 +60,11 @@ void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_navigation_mesh) {
}
void NavigationMeshGenerator::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
- 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().");
- ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
- ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified.");
- ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
-
- Vector<float> vertices;
- Vector<int> indices;
-
- List<Node *> parse_nodes;
-
- if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
- parse_nodes.push_back(p_root_node);
- } else {
- p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes);
- }
-
- Transform3D navmesh_xform = Transform3D();
- if (Object::cast_to<Node3D>(p_root_node)) {
- navmesh_xform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse();
- }
- for (Node *E : parse_nodes) {
- NavigationMesh::ParsedGeometryType geometry_type = p_navigation_mesh->get_parsed_geometry_type();
- uint32_t collision_mask = p_navigation_mesh->get_collision_mask();
- bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
- _parse_geometry(navmesh_xform, E, vertices, indices, geometry_type, collision_mask, recurse_children);
- }
-
- p_source_geometry_data->set_vertices(vertices);
- p_source_geometry_data->set_indices(indices);
-
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- //
- }
- }
+ NavigationServer3D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
}
void NavigationMeshGenerator::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
- ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
- ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
- ERR_FAIL_COND_MSG(!p_source_geometry_data->has_data(), "NavigationMeshSourceGeometryData3D is empty. Parse source geometry first.");
-
- generator_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- generator_mutex.unlock();
- ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
- } else {
- baking_navmeshes.insert(p_navigation_mesh);
- generator_mutex.unlock();
- }
-
-#ifndef _3D_DISABLED
- const Vector<float> vertices = p_source_geometry_data->get_vertices();
- const Vector<int> indices = p_source_geometry_data->get_indices();
-
- if (vertices.size() < 3 || indices.size() < 3) {
- return;
- }
-
- rcHeightfield *hf = nullptr;
- rcCompactHeightfield *chf = nullptr;
- rcContourSet *cset = nullptr;
- rcPolyMesh *poly_mesh = nullptr;
- rcPolyMeshDetail *detail_mesh = nullptr;
- rcContext ctx;
-
- // added to keep track of steps, no functionality right now
- String bake_state = "";
-
- bake_state = "Setting up Configuration..."; // step #1
-
- const float *verts = vertices.ptr();
- const int nverts = vertices.size() / 3;
- const int *tris = indices.ptr();
- const int ntris = indices.size() / 3;
-
- float bmin[3], bmax[3];
- rcCalcBounds(verts, nverts, bmin, bmax);
-
- rcConfig cfg;
- memset(&cfg, 0, sizeof(cfg));
-
- cfg.cs = p_navigation_mesh->get_cell_size();
- cfg.ch = p_navigation_mesh->get_cell_height();
- cfg.walkableSlopeAngle = p_navigation_mesh->get_agent_max_slope();
- cfg.walkableHeight = (int)Math::ceil(p_navigation_mesh->get_agent_height() / cfg.ch);
- cfg.walkableClimb = (int)Math::floor(p_navigation_mesh->get_agent_max_climb() / cfg.ch);
- cfg.walkableRadius = (int)Math::ceil(p_navigation_mesh->get_agent_radius() / cfg.cs);
- cfg.maxEdgeLen = (int)(p_navigation_mesh->get_edge_max_length() / p_navigation_mesh->get_cell_size());
- cfg.maxSimplificationError = p_navigation_mesh->get_edge_max_error();
- cfg.minRegionArea = (int)(p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size());
- cfg.mergeRegionArea = (int)(p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size());
- cfg.maxVertsPerPoly = (int)p_navigation_mesh->get_vertices_per_polygon();
- cfg.detailSampleDist = MAX(p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance(), 0.1f);
- cfg.detailSampleMaxError = p_navigation_mesh->get_cell_height() * p_navigation_mesh->get_detail_sample_max_error();
-
- if (!Math::is_equal_approx((float)cfg.walkableHeight * cfg.ch, p_navigation_mesh->get_agent_height())) {
- WARN_PRINT("Property agent_height is ceiled to cell_height voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.walkableClimb * cfg.ch, p_navigation_mesh->get_agent_max_climb())) {
- WARN_PRINT("Property agent_max_climb is floored to cell_height voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.walkableRadius * cfg.cs, p_navigation_mesh->get_agent_radius())) {
- WARN_PRINT("Property agent_radius is ceiled to cell_size voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.maxEdgeLen * cfg.cs, p_navigation_mesh->get_edge_max_length())) {
- WARN_PRINT("Property edge_max_length is rounded to cell_size voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.minRegionArea, p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size())) {
- WARN_PRINT("Property region_min_size is converted to int and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.mergeRegionArea, p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size())) {
- WARN_PRINT("Property region_merge_size is converted to int and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.maxVertsPerPoly, p_navigation_mesh->get_vertices_per_polygon())) {
- WARN_PRINT("Property vertices_per_polygon is converted to int and loses precision.");
- }
- if (p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance() < 0.1f) {
- WARN_PRINT("Property detail_sample_distance is clamped to 0.1 world units as the resulting value from multiplying with cell_size is too low.");
- }
-
- cfg.bmin[0] = bmin[0];
- cfg.bmin[1] = bmin[1];
- cfg.bmin[2] = bmin[2];
- cfg.bmax[0] = bmax[0];
- cfg.bmax[1] = bmax[1];
- cfg.bmax[2] = bmax[2];
-
- AABB baking_aabb = p_navigation_mesh->get_filter_baking_aabb();
- if (baking_aabb.has_volume()) {
- Vector3 baking_aabb_offset = p_navigation_mesh->get_filter_baking_aabb_offset();
- cfg.bmin[0] = baking_aabb.position[0] + baking_aabb_offset.x;
- cfg.bmin[1] = baking_aabb.position[1] + baking_aabb_offset.y;
- cfg.bmin[2] = baking_aabb.position[2] + baking_aabb_offset.z;
- cfg.bmax[0] = cfg.bmin[0] + baking_aabb.size[0];
- cfg.bmax[1] = cfg.bmin[1] + baking_aabb.size[1];
- cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2];
- }
-
- bake_state = "Calculating grid size..."; // step #2
- rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
-
- // ~30000000 seems to be around sweetspot where Editor baking breaks
- if ((cfg.width * cfg.height) > 30000000) {
- WARN_PRINT("NavigationMesh baking process will likely fail."
- "\nSource geometry is suspiciously big for the current Cell Size and Cell Height in the NavMesh Resource bake settings."
- "\nIf baking does not fail, the resulting NavigationMesh will create serious pathfinding performance issues."
- "\nIt is advised to increase Cell Size and/or Cell Height in the NavMesh Resource bake settings or reduce the size / scale of the source geometry.");
- }
-
- bake_state = "Creating heightfield..."; // step #3
- hf = rcAllocHeightfield();
-
- ERR_FAIL_COND(!hf);
- ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
-
- bake_state = "Marking walkable triangles..."; // step #4
- {
- Vector<unsigned char> tri_areas;
- tri_areas.resize(ntris);
-
- ERR_FAIL_COND(tri_areas.size() == 0);
-
- memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
- rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
-
- ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
- }
-
- if (p_navigation_mesh->get_filter_low_hanging_obstacles()) {
- rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
- }
- if (p_navigation_mesh->get_filter_ledge_spans()) {
- rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
- }
- if (p_navigation_mesh->get_filter_walkable_low_height_spans()) {
- rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
- }
-
- bake_state = "Constructing compact heightfield..."; // step #5
-
- chf = rcAllocCompactHeightfield();
-
- ERR_FAIL_COND(!chf);
- ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
-
- rcFreeHeightField(hf);
- hf = nullptr;
-
- bake_state = "Eroding walkable area..."; // step #6
-
- ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
-
- bake_state = "Partitioning..."; // step #7
-
- if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
- ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
- ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
- } else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
- ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
- } else {
- ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
- }
-
- bake_state = "Creating contours..."; // step #8
-
- cset = rcAllocContourSet();
-
- ERR_FAIL_COND(!cset);
- ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
-
- bake_state = "Creating polymesh..."; // step #9
-
- poly_mesh = rcAllocPolyMesh();
- ERR_FAIL_COND(!poly_mesh);
- ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
-
- detail_mesh = rcAllocPolyMeshDetail();
- ERR_FAIL_COND(!detail_mesh);
- ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
-
- rcFreeCompactHeightfield(chf);
- chf = nullptr;
- rcFreeContourSet(cset);
- cset = nullptr;
-
- bake_state = "Converting to native navigation mesh..."; // step #10
-
- Vector<Vector3> nav_vertices;
-
- for (int i = 0; i < detail_mesh->nverts; i++) {
- const float *v = &detail_mesh->verts[i * 3];
- nav_vertices.push_back(Vector3(v[0], v[1], v[2]));
- }
- p_navigation_mesh->set_vertices(nav_vertices);
- p_navigation_mesh->clear_polygons();
-
- for (int i = 0; i < detail_mesh->nmeshes; i++) {
- const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4];
- const unsigned int detail_mesh_bverts = detail_mesh_m[0];
- const unsigned int detail_mesh_m_btris = detail_mesh_m[2];
- const unsigned int detail_mesh_ntris = detail_mesh_m[3];
- const unsigned char *detail_mesh_tris = &detail_mesh->tris[detail_mesh_m_btris * 4];
- for (unsigned int j = 0; j < detail_mesh_ntris; j++) {
- Vector<int> nav_indices;
- nav_indices.resize(3);
- // Polygon order in recast is opposite than godot's
- nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0]));
- nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2]));
- nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1]));
- p_navigation_mesh->add_polygon(nav_indices);
- }
- }
-
- bake_state = "Cleanup..."; // step #11
-
- rcFreePolyMesh(poly_mesh);
- poly_mesh = nullptr;
- rcFreePolyMeshDetail(detail_mesh);
- detail_mesh = nullptr;
-
- bake_state = "Baking finished."; // step #12
-#endif // _3D_DISABLED
-
- generator_mutex.lock();
- baking_navmeshes.erase(p_navigation_mesh);
- generator_mutex.unlock();
-
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- //
- }
- }
+ NavigationServer3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
}
void NavigationMeshGenerator::_bind_methods() {
diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h
index 4bf2b64f44..08fe9f9142 100644
--- a/modules/navigation/navigation_mesh_generator.h
+++ b/modules/navigation/navigation_mesh_generator.h
@@ -36,27 +36,16 @@
#include "scene/3d/navigation_region_3d.h"
#include "scene/resources/navigation_mesh.h"
-#include <Recast.h>
-
class NavigationMeshSourceGeometryData3D;
class NavigationMeshGenerator : public Object {
GDCLASS(NavigationMeshGenerator, Object);
- Mutex generator_mutex;
static NavigationMeshGenerator *singleton;
- HashSet<Ref<NavigationMesh>> baking_navmeshes;
-
protected:
static void _bind_methods();
- static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices);
- static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _add_mesh_array(const Array &p_array, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children);
-
public:
static NavigationMeshGenerator *get_singleton();
diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp
index 1401833d0e..525fe71134 100644
--- a/modules/navigation/register_types.cpp
+++ b/modules/navigation/register_types.cpp
@@ -31,35 +31,48 @@
#include "register_types.h"
#include "godot_navigation_server.h"
+#include "godot_navigation_server_2d.h"
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
#include "navigation_mesh_generator.h"
#endif
+#endif // DISABLE_DEPRECATED
#ifdef TOOLS_ENABLED
#include "editor/navigation_mesh_editor_plugin.h"
#endif
#include "core/config/engine.h"
+#include "servers/navigation_server_2d.h"
#include "servers/navigation_server_3d.h"
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
NavigationMeshGenerator *_nav_mesh_generator = nullptr;
#endif
+#endif // DISABLE_DEPRECATED
NavigationServer3D *new_server() {
return memnew(GodotNavigationServer);
}
+NavigationServer2D *new_navigation_server_2d() {
+ return memnew(GodotNavigationServer2D);
+}
+
void initialize_navigation_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
NavigationServer3DManager::set_default_server(new_server);
+ NavigationServer2DManager::set_default_server(new_navigation_server_2d);
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
_nav_mesh_generator = memnew(NavigationMeshGenerator);
GDREGISTER_CLASS(NavigationMeshGenerator);
Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationMeshGenerator", NavigationMeshGenerator::get_singleton()));
#endif
+#endif // DISABLE_DEPRECATED
}
#ifdef TOOLS_ENABLED
@@ -74,9 +87,11 @@ void uninitialize_navigation_module(ModuleInitializationLevel p_level) {
return;
}
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
if (_nav_mesh_generator) {
memdelete(_nav_mesh_generator);
}
#endif
+#endif // DISABLE_DEPRECATED
}
diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml
index 4c6cdfbf12..f2a6c60376 100644
--- a/modules/noise/doc_classes/FastNoiseLite.xml
+++ b/modules/noise/doc_classes/FastNoiseLite.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
This class generates noise using the FastNoiseLite library, which is a collection of several noise algorithms including Cellular, Perlin, Value, and more.
- Most generated noise values are in the range of [code][-1,1][/code], however not always. Some of the cellular noise algorithms return results above [code]1[/code].
+ Most generated noise values are in the range of [code][-1, 1][/code], but not always. Some of the cellular noise algorithms return results above [code]1[/code].
</description>
<tutorials>
</tutorials>
@@ -13,7 +13,7 @@
<member name="cellular_distance_function" type="int" setter="set_cellular_distance_function" getter="get_cellular_distance_function" enum="FastNoiseLite.CellularDistanceFunction" default="0">
Determines how the distance to the nearest/second-nearest point is computed. See [enum CellularDistanceFunction] for options.
</member>
- <member name="cellular_jitter" type="float" setter="set_cellular_jitter" getter="get_cellular_jitter" default="0.45">
+ <member name="cellular_jitter" type="float" setter="set_cellular_jitter" getter="get_cellular_jitter" default="1.0">
Maximum distance a point can move off of its grid position. Set to [code]0[/code] for an even grid.
</member>
<member name="cellular_return_type" type="int" setter="set_cellular_return_type" getter="get_cellular_return_type" enum="FastNoiseLite.CellularReturnType" default="1">
diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml
index dd232af1cc..7d74c84f93 100644
--- a/modules/noise/doc_classes/Noise.xml
+++ b/modules/noise/doc_classes/Noise.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
This class defines the interface for noise generation libraries to inherit from.
- A default get_seamless_noise() implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from get_image(), reverses the quadrants of the image, then uses the strips of extra width to blend over the seams.
+ A default [method get_seamless_image] implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from the [method get_image] method, reverses the quadrants of the image, then uses the strips of extra width to blend over the seams.
Inheriting noise classes can optionally override this function to provide a more optimal algorithm.
</description>
<tutorials>
diff --git a/modules/noise/doc_classes/NoiseTexture2D.xml b/modules/noise/doc_classes/NoiseTexture2D.xml
index e25af794d4..3f4afc5141 100644
--- a/modules/noise/doc_classes/NoiseTexture2D.xml
+++ b/modules/noise/doc_classes/NoiseTexture2D.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NoiseTexture2D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- A texture filled with noise generated by a [Noise] object.
+ A 2D texture filled with noise generated by a [Noise] object.
</brief_description>
<description>
- Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures.
+ Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures.
The class uses [Thread]s to generate the texture data internally, so [method Texture2D.get_image] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image and the generated byte data:
[codeblock]
var texture = NoiseTexture2D.new()
diff --git a/modules/noise/doc_classes/NoiseTexture3D.xml b/modules/noise/doc_classes/NoiseTexture3D.xml
index 0ada6942ad..e8e205bc68 100644
--- a/modules/noise/doc_classes/NoiseTexture3D.xml
+++ b/modules/noise/doc_classes/NoiseTexture3D.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NoiseTexture3D" inherits="Texture3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- A texture filled with noise generated by a [Noise] object.
+ A 3D texture filled with noise generated by a [Noise] object.
</brief_description>
<description>
- Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size.
+ Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size.
The class uses [Thread]s to generate the texture data internally, so [method Texture3D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image:
[codeblock]
var texture = NoiseTexture3D.new()
diff --git a/modules/noise/fastnoise_lite.h b/modules/noise/fastnoise_lite.h
index c63f7d7d29..06a2b39abe 100644
--- a/modules/noise/fastnoise_lite.h
+++ b/modules/noise/fastnoise_lite.h
@@ -116,7 +116,7 @@ private:
// Cellular specific.
CellularDistanceFunction cellular_distance_function = DISTANCE_EUCLIDEAN;
CellularReturnType cellular_return_type = RETURN_DISTANCE;
- real_t cellular_jitter = 0.45;
+ real_t cellular_jitter = 1.0;
// Domain warp specific.
bool domain_warp_enabled = false;
diff --git a/modules/noise/icons/NoiseTexture3D.svg b/modules/noise/icons/NoiseTexture3D.svg
new file mode 100644
index 0000000000..92da633dce
--- /dev/null
+++ b/modules/noise/icons/NoiseTexture3D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 14a1 1 0 0 0 1 1h9.5a1 1 0 0 0 .707-.293l2.5-2.5A1 1 0 0 0 15 11.5V2a1 1 0 0 0-1-1H4.5a1 1 0 0 0-.707.293l-2.5 2.5A1 1 0 0 0 1 4.5zm1.25-9H11v7H2.25zm10 6.25v-6.5L14 3v6.5zm-1-7.5H3L4.75 2H13zM3 11h4l1.25-1.25V9H9l1.25-1.25v-2h-2L7 7v.75h-.75v-2h-2L3 7z" fill="#e0e0e0"/><path d="M3 7h2l1.25-1.25h-2zm2 2h2V7.75h-.75zm2-2h2l1.25-1.25H8z" fill="#000" fill-opacity=".4"/><path d="M5 7v2l1.25-1.25v-2zm2 2v2l1.25-1.25V9zm2 0V7l1.25-1.25v2z" fill="#000" fill-opacity=".2"/></svg>
diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp
index ed242e7faa..33e257a5c2 100644
--- a/modules/noise/noise_texture_3d.cpp
+++ b/modules/noise/noise_texture_3d.cpp
@@ -110,6 +110,7 @@ void NoiseTexture3D::_set_texture_data(const TypedArray<Image> &p_data) {
} else {
texture = RS::get_singleton()->texture_3d_create(data[0]->get_format(), data[0]->get_width(), data[0]->get_height(), data.size(), false, data);
}
+ format = data[0]->get_format();
}
emit_changed();
}
@@ -346,6 +347,5 @@ Vector<Ref<Image>> NoiseTexture3D::get_data() const {
}
Image::Format NoiseTexture3D::get_format() const {
- ERR_FAIL_COND_V(!texture.is_valid(), Image::FORMAT_L8);
- return RS::get_singleton()->texture_3d_get(texture)[0]->get_format();
+ return format;
}
diff --git a/modules/noise/noise_texture_3d.h b/modules/noise/noise_texture_3d.h
index 397711ca98..13125efe7f 100644
--- a/modules/noise/noise_texture_3d.h
+++ b/modules/noise/noise_texture_3d.h
@@ -60,6 +60,8 @@ private:
Ref<Gradient> color_ramp;
Ref<Noise> noise;
+ Image::Format format = Image::FORMAT_L8;
+
void _thread_done(const TypedArray<Image> &p_data);
static void _thread_function(void *p_ud);
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index f49dc390de..c3a5d82fc4 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -52,10 +52,11 @@ if env["builtin_openxr"]:
env_thirdparty = env_openxr.Clone()
env_thirdparty.disable_warnings()
+
env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"])
+ if env["disable_exceptions"]:
+ env_thirdparty.AppendUnique(CPPDEFINES=["XRLOADER_DISABLE_EXCEPTION_HANDLING", ("JSON_USE_EXCEPTION", 0)])
- if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]:
- env_thirdparty["CXXFLAGS"].remove("-fno-exceptions")
env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"])
# add in external jsoncpp dependency
@@ -109,6 +110,8 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extens
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")
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 63abbf0d71..6d79e33de8 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -308,14 +308,14 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Pico 4 / Neo 3 controller profile
- profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/pico/neo3_controller");
+ // Create our Pico 4 controller profile.
+ profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/bytedance/pico4_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available
- profile->add_new_binding(menu_button, "/user/hand/left/input/back/click,/user/hand/right/input/back/click"); // right hand back click may not be available
+ profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click");
profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch");
profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
@@ -470,6 +470,7 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
+ add_interaction_profile(profile);
// Create our HTC Vive tracker profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_tracker_htcx");
@@ -547,7 +548,7 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref<OpenXRAction> p
for (int i = 0; i < interaction_profiles.size(); i++) {
Ref<OpenXRInteractionProfile> ip = interaction_profiles[i];
- const OpenXRInteractionProfileMetaData::InteractionProfile *profile = OpenXRInteractionProfileMetaData::get_singleton()->get_profile(ip->get_interaction_profile_path());
+ const OpenXRInteractionProfileMetadata::InteractionProfile *profile = OpenXRInteractionProfileMetadata::get_singleton()->get_profile(ip->get_interaction_profile_path());
if (profile != nullptr) {
for (int j = 0; j < ip->get_binding_count(); j++) {
@@ -556,7 +557,7 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref<OpenXRAction> p
PackedStringArray paths = binding->get_paths();
for (int k = 0; k < paths.size(); k++) {
- const OpenXRInteractionProfileMetaData::IOPath *io_path = profile->get_io_path(paths[k]);
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(paths[k]);
if (io_path != nullptr) {
String top_path = io_path->top_level_path;
diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp
index 07490f5841..65ee652732 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile.cpp
@@ -126,7 +126,10 @@ Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char *
}
void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) {
- interaction_profile_path = p_input_profile_path;
+ OpenXRInteractionProfileMetadata *pmd = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(pmd);
+
+ interaction_profile_path = pmd->check_profile_name(p_input_profile_path);
emit_changed();
}
diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h
index 4a82785f14..479cc3c527 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.h
+++ b/modules/openxr/action_map/openxr_interaction_profile.h
@@ -32,7 +32,7 @@
#define OPENXR_INTERACTION_PROFILE_H
#include "openxr_action.h"
-#include "openxr_interaction_profile_meta_data.h"
+#include "openxr_interaction_profile_metadata.h"
#include "core/io/resource.h"
diff --git a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp
index 11449bdfb4..df607b0def 100644
--- a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* openxr_interaction_profile_meta_data.cpp */
+/* openxr_interaction_profile_metadata.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,30 +28,45 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "openxr_interaction_profile_meta_data.h"
+#include "openxr_interaction_profile_metadata.h"
#include "../openxr_api.h"
-OpenXRInteractionProfileMetaData *OpenXRInteractionProfileMetaData::singleton = nullptr;
+OpenXRInteractionProfileMetadata *OpenXRInteractionProfileMetadata::singleton = nullptr;
-OpenXRInteractionProfileMetaData::OpenXRInteractionProfileMetaData() {
+OpenXRInteractionProfileMetadata::OpenXRInteractionProfileMetadata() {
singleton = this;
_register_core_metadata();
OpenXRAPI::register_extension_metadata();
}
-OpenXRInteractionProfileMetaData::~OpenXRInteractionProfileMetaData() {
+OpenXRInteractionProfileMetadata::~OpenXRInteractionProfileMetadata() {
singleton = nullptr;
}
-void OpenXRInteractionProfileMetaData::_bind_methods() {
- ClassDB::bind_method(D_METHOD("register_top_level_path", "display_name", "openxr_path", "openxr_extension_name"), &OpenXRInteractionProfileMetaData::register_top_level_path);
- ClassDB::bind_method(D_METHOD("register_interaction_profile", "display_name", "openxr_path", "openxr_extension_name"), &OpenXRInteractionProfileMetaData::register_interaction_profile);
- ClassDB::bind_method(D_METHOD("register_io_path", "interaction_profile", "display_name", "toplevel_path", "openxr_path", "openxr_extension_name", "action_type"), &OpenXRInteractionProfileMetaData::register_io_path);
+void OpenXRInteractionProfileMetadata::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("register_profile_rename", "old_name", "new_name"), &OpenXRInteractionProfileMetadata::register_profile_rename);
+ ClassDB::bind_method(D_METHOD("register_top_level_path", "display_name", "openxr_path", "openxr_extension_name"), &OpenXRInteractionProfileMetadata::register_top_level_path);
+ ClassDB::bind_method(D_METHOD("register_interaction_profile", "display_name", "openxr_path", "openxr_extension_name"), &OpenXRInteractionProfileMetadata::register_interaction_profile);
+ ClassDB::bind_method(D_METHOD("register_io_path", "interaction_profile", "display_name", "toplevel_path", "openxr_path", "openxr_extension_name", "action_type"), &OpenXRInteractionProfileMetadata::register_io_path);
}
-void OpenXRInteractionProfileMetaData::register_top_level_path(const String &p_display_name, const String &p_openxr_path, const String &p_openxr_extension_name) {
+void OpenXRInteractionProfileMetadata::register_profile_rename(const String &p_old_name, const String &p_new_name) {
+ ERR_FAIL_COND(profile_renames.has(p_old_name));
+
+ profile_renames[p_old_name] = p_new_name;
+}
+
+String OpenXRInteractionProfileMetadata::check_profile_name(const String &p_name) const {
+ if (profile_renames.has(p_name)) {
+ return profile_renames[p_name];
+ }
+
+ return p_name;
+}
+
+void OpenXRInteractionProfileMetadata::register_top_level_path(const String &p_display_name, const String &p_openxr_path, const String &p_openxr_extension_name) {
ERR_FAIL_COND_MSG(has_top_level_path(p_openxr_path), p_openxr_path + " had already been registered");
TopLevelPath new_toplevel_path = {
@@ -63,7 +78,7 @@ void OpenXRInteractionProfileMetaData::register_top_level_path(const String &p_d
top_level_paths.push_back(new_toplevel_path);
}
-void OpenXRInteractionProfileMetaData::register_interaction_profile(const String &p_display_name, const String &p_openxr_path, const String &p_openxr_extension_name) {
+void OpenXRInteractionProfileMetadata::register_interaction_profile(const String &p_display_name, const String &p_openxr_path, const String &p_openxr_extension_name) {
ERR_FAIL_COND_MSG(has_interaction_profile(p_openxr_path), p_openxr_path + " has already been registered");
InteractionProfile new_profile;
@@ -74,7 +89,7 @@ void OpenXRInteractionProfileMetaData::register_interaction_profile(const String
interaction_profiles.push_back(new_profile);
}
-void OpenXRInteractionProfileMetaData::register_io_path(const String &p_interaction_profile, const String &p_display_name, const String &p_toplevel_path, const String &p_openxr_path, const String &p_openxr_extension_name, OpenXRAction::ActionType p_action_type) {
+void OpenXRInteractionProfileMetadata::register_io_path(const String &p_interaction_profile, const String &p_display_name, const String &p_toplevel_path, const String &p_openxr_path, const String &p_openxr_extension_name, OpenXRAction::ActionType p_action_type) {
ERR_FAIL_COND_MSG(!has_interaction_profile(p_interaction_profile), "Unknown interaction profile " + p_interaction_profile);
ERR_FAIL_COND_MSG(!has_top_level_path(p_toplevel_path), "Unknown top level path " + p_toplevel_path);
@@ -95,7 +110,7 @@ void OpenXRInteractionProfileMetaData::register_io_path(const String &p_interact
}
}
-bool OpenXRInteractionProfileMetaData::has_top_level_path(const String p_openxr_path) const {
+bool OpenXRInteractionProfileMetadata::has_top_level_path(const String p_openxr_path) const {
for (int i = 0; i < top_level_paths.size(); i++) {
if (top_level_paths[i].openxr_path == p_openxr_path) {
return true;
@@ -105,7 +120,7 @@ bool OpenXRInteractionProfileMetaData::has_top_level_path(const String p_openxr_
return false;
}
-String OpenXRInteractionProfileMetaData::get_top_level_name(const String p_openxr_path) const {
+String OpenXRInteractionProfileMetadata::get_top_level_name(const String p_openxr_path) const {
for (int i = 0; i < top_level_paths.size(); i++) {
if (top_level_paths[i].openxr_path == p_openxr_path) {
return top_level_paths[i].display_name;
@@ -115,7 +130,7 @@ String OpenXRInteractionProfileMetaData::get_top_level_name(const String p_openx
return String();
}
-String OpenXRInteractionProfileMetaData::get_top_level_extension(const String p_openxr_path) const {
+String OpenXRInteractionProfileMetadata::get_top_level_extension(const String p_openxr_path) const {
for (int i = 0; i < top_level_paths.size(); i++) {
if (top_level_paths[i].openxr_path == p_openxr_path) {
return top_level_paths[i].openxr_extension_name;
@@ -125,7 +140,7 @@ String OpenXRInteractionProfileMetaData::get_top_level_extension(const String p_
return XR_PATH_UNSUPPORTED_NAME;
}
-bool OpenXRInteractionProfileMetaData::has_interaction_profile(const String p_openxr_path) const {
+bool OpenXRInteractionProfileMetadata::has_interaction_profile(const String p_openxr_path) const {
for (int i = 0; i < interaction_profiles.size(); i++) {
if (interaction_profiles[i].openxr_path == p_openxr_path) {
return true;
@@ -135,7 +150,7 @@ bool OpenXRInteractionProfileMetaData::has_interaction_profile(const String p_op
return false;
}
-String OpenXRInteractionProfileMetaData::get_interaction_profile_extension(const String p_openxr_path) const {
+String OpenXRInteractionProfileMetadata::get_interaction_profile_extension(const String p_openxr_path) const {
for (int i = 0; i < interaction_profiles.size(); i++) {
if (interaction_profiles[i].openxr_path == p_openxr_path) {
return interaction_profiles[i].openxr_extension_name;
@@ -145,7 +160,7 @@ String OpenXRInteractionProfileMetaData::get_interaction_profile_extension(const
return XR_PATH_UNSUPPORTED_NAME;
}
-const OpenXRInteractionProfileMetaData::InteractionProfile *OpenXRInteractionProfileMetaData::get_profile(const String p_openxr_path) const {
+const OpenXRInteractionProfileMetadata::InteractionProfile *OpenXRInteractionProfileMetadata::get_profile(const String p_openxr_path) const {
for (int i = 0; i < interaction_profiles.size(); i++) {
if (interaction_profiles[i].openxr_path == p_openxr_path) {
return &interaction_profiles[i];
@@ -155,7 +170,7 @@ const OpenXRInteractionProfileMetaData::InteractionProfile *OpenXRInteractionPro
return nullptr;
}
-bool OpenXRInteractionProfileMetaData::InteractionProfile::has_io_path(const String p_io_path) const {
+bool OpenXRInteractionProfileMetadata::InteractionProfile::has_io_path(const String p_io_path) const {
for (int i = 0; i < io_paths.size(); i++) {
if (io_paths[i].openxr_path == p_io_path) {
return true;
@@ -165,7 +180,7 @@ bool OpenXRInteractionProfileMetaData::InteractionProfile::has_io_path(const Str
return false;
}
-const OpenXRInteractionProfileMetaData::IOPath *OpenXRInteractionProfileMetaData::InteractionProfile::get_io_path(const String p_io_path) const {
+const OpenXRInteractionProfileMetadata::IOPath *OpenXRInteractionProfileMetadata::InteractionProfile::get_io_path(const String p_io_path) const {
for (int i = 0; i < io_paths.size(); i++) {
if (io_paths[i].openxr_path == p_io_path) {
return &io_paths[i];
@@ -175,8 +190,8 @@ const OpenXRInteractionProfileMetaData::IOPath *OpenXRInteractionProfileMetaData
return nullptr;
}
-const OpenXRInteractionProfileMetaData::IOPath *OpenXRInteractionProfileMetaData::get_io_path(const String p_interaction_profile, const String p_io_path) const {
- const OpenXRInteractionProfileMetaData::InteractionProfile *profile = get_profile(p_interaction_profile);
+const OpenXRInteractionProfileMetadata::IOPath *OpenXRInteractionProfileMetadata::get_io_path(const String p_interaction_profile, const String p_io_path) const {
+ const OpenXRInteractionProfileMetadata::InteractionProfile *profile = get_profile(p_interaction_profile);
if (profile != nullptr) {
return profile->get_io_path(p_io_path);
}
@@ -184,7 +199,7 @@ const OpenXRInteractionProfileMetaData::IOPath *OpenXRInteractionProfileMetaData
return nullptr;
}
-PackedStringArray OpenXRInteractionProfileMetaData::get_interaction_profile_paths() const {
+PackedStringArray OpenXRInteractionProfileMetadata::get_interaction_profile_paths() const {
PackedStringArray arr;
for (int i = 0; i < interaction_profiles.size(); i++) {
@@ -194,7 +209,7 @@ PackedStringArray OpenXRInteractionProfileMetaData::get_interaction_profile_path
return arr;
}
-void OpenXRInteractionProfileMetaData::_register_core_metadata() {
+void OpenXRInteractionProfileMetadata::_register_core_metadata() {
// Note, currently we add definitions that belong in extensions.
// Extensions are registered when our OpenXRAPI is instantiated
// however this does not happen in the editor.
diff --git a/modules/openxr/action_map/openxr_interaction_profile_meta_data.h b/modules/openxr/action_map/openxr_interaction_profile_metadata.h
index 3eab2139ff..252e36f0f6 100644
--- a/modules/openxr/action_map/openxr_interaction_profile_meta_data.h
+++ b/modules/openxr/action_map/openxr_interaction_profile_metadata.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* openxr_interaction_profile_meta_data.h */
+/* openxr_interaction_profile_metadata.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,11 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef OPENXR_INTERACTION_PROFILE_META_DATA_H
-#define OPENXR_INTERACTION_PROFILE_META_DATA_H
+#ifndef OPENXR_INTERACTION_PROFILE_METADATA_H
+#define OPENXR_INTERACTION_PROFILE_METADATA_H
///////////////////////////////////////////////////////////////////////////
-// Stores available interaction profile meta data
+// Stores available interaction profile metadata
//
// OpenXR defines and hardcodes all the supported input devices and their
// paths as part of the OpenXR spec. When support for new devices is
@@ -54,10 +54,13 @@
#include "openxr_action.h"
#include "core/object/object.h"
+#include "core/templates/hash_map.h"
#define XR_PATH_UNSUPPORTED_NAME "unsupported"
-class OpenXRInteractionProfileMetaData : public Object {
+class OpenXRInteractionProfileMetadata : public Object {
+ GDCLASS(OpenXRInteractionProfileMetadata, Object);
+
public:
struct TopLevelPath {
String display_name; // User friendly display name (i.e. Left controller)
@@ -84,8 +87,9 @@ public:
};
private:
- static OpenXRInteractionProfileMetaData *singleton;
+ static OpenXRInteractionProfileMetadata *singleton;
+ HashMap<String, String> profile_renames;
Vector<TopLevelPath> top_level_paths;
Vector<InteractionProfile> interaction_profiles;
@@ -95,10 +99,13 @@ protected:
static void _bind_methods();
public:
- static OpenXRInteractionProfileMetaData *get_singleton() { return singleton; }
+ static OpenXRInteractionProfileMetadata *get_singleton() { return singleton; }
+
+ OpenXRInteractionProfileMetadata();
+ ~OpenXRInteractionProfileMetadata();
- OpenXRInteractionProfileMetaData();
- ~OpenXRInteractionProfileMetaData();
+ void register_profile_rename(const String &p_old_name, const String &p_new_name);
+ String check_profile_name(const String &p_name) const;
void register_top_level_path(const String &p_display_name, const String &p_openxr_path, const String &p_openxr_extension_name);
bool has_top_level_path(const String p_openxr_path) const;
@@ -115,4 +122,4 @@ public:
const IOPath *get_io_path(const String p_interaction_profile, const String p_io_path) const;
};
-#endif // OPENXR_INTERACTION_PROFILE_META_DATA_H
+#endif // OPENXR_INTERACTION_PROFILE_METADATA_H
diff --git a/modules/openxr/config.py b/modules/openxr/config.py
index 8ed06a1606..92aaf06fce 100644
--- a/modules/openxr/config.py
+++ b/modules/openxr/config.py
@@ -16,7 +16,10 @@ def get_doc_classes():
"OpenXRAction",
"OpenXRActionSet",
"OpenXRActionMap",
+ "OpenXRAPIExtension",
+ "OpenXRExtensionWrapperExtension",
"OpenXRInteractionProfile",
+ "OpenXRInteractionProfileMetadata",
"OpenXRIPBinding",
"OpenXRHand",
]
diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
new file mode 100644
index 0000000000..e795311651
--- /dev/null
+++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="OpenXRAPIExtension" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Makes the OpenXR API available for GDExtension.
+ </brief_description>
+ <description>
+ [OpenXRAPIExtension] makes OpenXR available for GDExtension. It provides the OpenXR API to GDExtension through the [method get_instance_proc_addr] method, and the OpenXR instance through [method get_instance].
+ It also provides methods for querying the status of OpenXR initialization, and helper methods for ease of use of the API with GDExtension.
+ </description>
+ <tutorials>
+ <link title="XrResult documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrResult.html</link>
+ <link title="XrInstance documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrInstance.html</link>
+ <link title="XrSpace documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSpace.html</link>
+ <link title="XrSession documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSession.html</link>
+ <link title="XrSystemId documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html</link>
+ <link title="xrBeginSession documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrBeginSession.html</link>
+ <link title="XrPosef documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html</link>
+ </tutorials>
+ <methods>
+ <method name="can_render">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXR is initialized for rendering with an XR viewport.
+ </description>
+ </method>
+ <method name="get_error_string">
+ <return type="String" />
+ <param index="0" name="result" type="int" />
+ <description>
+ Returns an error string for the given [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrResult.html]XrResult[/url].
+ </description>
+ </method>
+ <method name="get_instance">
+ <return type="int" />
+ <description>
+ Returns the [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrInstance.html]XrInstance[/url] created during the initialization of the OpenXR API.
+ </description>
+ </method>
+ <method name="get_instance_proc_addr">
+ <return type="int" />
+ <param index="0" name="name" type="String" />
+ <description>
+ Returns the function pointer of the OpenXR function with the specified name, cast to an integer. If the function with the given name does not exist, the method returns [code]0[/code].
+ [b]Note:[/b] [code]openxr/util.h[/code] contains utility macros for acquiring OpenXR functions, e.g. [code]GDEXTENSION_INIT_XR_FUNC_V(xrCreateAction)[/code].
+ </description>
+ </method>
+ <method name="get_next_frame_time">
+ <return type="int" />
+ <description>
+ Returns the timing for the next frame.
+ </description>
+ </method>
+ <method name="get_play_space">
+ <return type="int" />
+ <description>
+ Returns the play space, which is an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSpace.html]XrSpace[/url] cast to an integer.
+ </description>
+ </method>
+ <method name="get_session">
+ <return type="int" />
+ <description>
+ Returns the OpenXR session, which is an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSession.html]XrSession[/url] cast to an integer.
+ </description>
+ </method>
+ <method name="get_swapchain_format_name">
+ <return type="String" />
+ <param index="0" name="swapchain_format" type="int" />
+ <description>
+ Returns the name of the specified swapchain format.
+ </description>
+ </method>
+ <method name="get_system_id">
+ <return type="int" />
+ <description>
+ Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer.
+ </description>
+ </method>
+ <method name="is_initialized">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXR is initialized.
+ </description>
+ </method>
+ <method name="is_running">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXR is running ([url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrBeginSession.html]xrBeginSession[/url] was successfully called and the swapchains were created).
+ </description>
+ </method>
+ <method name="openxr_is_enabled" qualifiers="static">
+ <return type="bool" />
+ <param index="0" name="check_run_in_editor" type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXR is enabled.
+ </description>
+ </method>
+ <method name="transform_from_pose">
+ <return type="Transform3D" />
+ <param index="0" name="pose" type="const void*" />
+ <description>
+ Creates a [Transform3D] from an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html]XrPosef[/url].
+ </description>
+ </method>
+ <method name="xr_result">
+ <return type="bool" />
+ <param index="0" name="result" type="int" />
+ <param index="1" name="format" type="String" />
+ <param index="2" name="args" type="Array" />
+ <description>
+ Returns [code]true[/code] if the provided [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrResult.html]XrResult[/url] (cast to an integer) is successful. Otherwise returns [code]false[/code] and prints the [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrResult.html]XrResult[/url] converted to a string, with the specified additional information.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
new file mode 100644
index 0000000000..e09b58e5b4
--- /dev/null
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="OpenXRExtensionWrapperExtension" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Allows clients to implement OpenXR extensions with GDExtension.
+ </brief_description>
+ <description>
+ [OpenXRExtensionWrapperExtension] allows clients to implement OpenXR extensions with GDExtension. The extension should be registered with [method register_extension_wrapper].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_get_requested_extensions" qualifiers="virtual">
+ <return type="Dictionary" />
+ <description>
+ Returns a [Dictionary] of OpenXR extensions related to this extension. The [Dictionary] should contain the name of the extension, mapped to a [code]bool *[/code] cast to an integer:
+ - If the [code]bool *[/code] is a [code]nullptr[/code] this extension is mandatory.
+ - If the [code]bool *[/code] points to a boolean, the boolean will be updated to [code]true[/code] if the extension is enabled.
+ </description>
+ </method>
+ <method name="_on_before_instance_created" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called before the OpenXR instance is created.
+ </description>
+ </method>
+ <method name="_on_event_polled" qualifiers="virtual">
+ <return type="bool" />
+ <param index="0" name="event" type="const void*" />
+ <description>
+ Called when there is an OpenXR event to process. When implementing, return [code]true[/code] if the event was handled, return [code]false[/code] otherwise.
+ </description>
+ </method>
+ <method name="_on_instance_created" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="instance" type="int" />
+ <description>
+ Called right after the OpenXR instance is created.
+ </description>
+ </method>
+ <method name="_on_instance_destroyed" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called right before the OpenXR instance is destroyed.
+ </description>
+ </method>
+ <method name="_on_pre_render" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called right before the XR viewports begin their rendering step.
+ </description>
+ </method>
+ <method name="_on_process" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called as part of the OpenXR process handling. This happens right before general and physics processing steps of the main loop. During this step controller data is queried and made available to game logic.
+ </description>
+ </method>
+ <method name="_on_register_metadata" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Allows extensions to register additional controller metadata. This function is called even when the OpenXR API is not constructed as the metadata needs to be available to the editor.
+ Extensions should also provide metadata regardless of whether they are supported on the host system. The controller data is used to setup action maps for users who may have access to the relevant hardware.
+ </description>
+ </method>
+ <method name="_on_session_created" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="session" type="int" />
+ <description>
+ Called right after the OpenXR session is created.
+ </description>
+ </method>
+ <method name="_on_session_destroyed" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called right before the OpenXR session is destroyed.
+ </description>
+ </method>
+ <method name="_on_state_exiting" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to exiting.
+ </description>
+ </method>
+ <method name="_on_state_focused" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to focused. This state is the active state when the game runs.
+ </description>
+ </method>
+ <method name="_on_state_idle" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to idle.
+ </description>
+ </method>
+ <method name="_on_state_loss_pending" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to loss pending.
+ </description>
+ </method>
+ <method name="_on_state_ready" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to ready. This means OpenXR is ready to set up the session.
+ </description>
+ </method>
+ <method name="_on_state_stopping" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to stopping.
+ </description>
+ </method>
+ <method name="_on_state_synchronized" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to synchronized. OpenXR also returns to this state when the application loses focus.
+ </description>
+ </method>
+ <method name="_on_state_visible" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ Called when the OpenXR session state is changed to visible. This means OpenXR is now ready to receive frames.
+ </description>
+ </method>
+ <method name="_set_instance_create_info_and_get_next_pointer" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="next_pointer" type="void*" />
+ <description>
+ Adds additional data structures when the OpenXR instance is created.
+ </description>
+ </method>
+ <method name="_set_session_create_and_get_next_pointer" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="next_pointer" type="void*" />
+ <description>
+ Adds additional data structures when the OpenXR session is created.
+ </description>
+ </method>
+ <method name="_set_swapchain_create_info_and_get_next_pointer" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="next_pointer" type="void*" />
+ <description>
+ Adds additional data structures when creating OpenXR swapchains.
+ </description>
+ </method>
+ <method name="_set_system_properties_and_get_next_pointer" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="next_pointer" type="void*" />
+ <description>
+ Adds additional data structures when interogating OpenXR system abilities.
+ </description>
+ </method>
+ <method name="get_openxr_api">
+ <return type="OpenXRAPIExtension" />
+ <description>
+ Returns the created [OpenXRAPIExtension], which can be used to access the OpenXR API.
+ </description>
+ </method>
+ <method name="register_extension_wrapper">
+ <return type="void" />
+ <description>
+ Registers the extension. This should happen at core module initialization level.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml
index a69bb875fa..ed5113f83c 100644
--- a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml
+++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml
@@ -4,7 +4,7 @@
Suggested bindings object for OpenXR.
</brief_description>
<description>
- This object stores suggested bindings for an interaction profile. Interaction profiles define the meta data for a tracked XR device such as an XR controller.
+ This object stores suggested bindings for an interaction profile. Interaction profiles define the metadata for a tracked XR device such as an XR controller.
For more information see the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-interaction-profiles]interaction profiles info in the OpenXR specification[/url].
</description>
<tutorials>
diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfileMetadata.xml b/modules/openxr/doc_classes/OpenXRInteractionProfileMetadata.xml
new file mode 100644
index 0000000000..c91d3569eb
--- /dev/null
+++ b/modules/openxr/doc_classes/OpenXRInteractionProfileMetadata.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="OpenXRInteractionProfileMetadata" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Meta class registering supported devices in OpenXR.
+ </brief_description>
+ <description>
+ This class allows OpenXR core and extensions to register metadata relating to supported interaction devices such as controllers, trackers, haptic devices, etc. It is primarily used by the action map editor and to sanitize any action map by removing extension-dependent entries when applicable.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="register_interaction_profile">
+ <return type="void" />
+ <param index="0" name="display_name" type="String" />
+ <param index="1" name="openxr_path" type="String" />
+ <param index="2" name="openxr_extension_name" type="String" />
+ <description>
+ Registers an interaction profile using its OpenXR designation (e.g. [code]/interaction_profiles/khr/simple_controller[/code] is the profile for OpenXR's simple controller profile).
+ [param display_name] is the description shown to the user. [param openxr_path] is the interaction profile path being registered. [param openxr_extension_name] optionally restricts this profile to the given extension being enabled/available. If the extension is not available, the profile and all related entries used in an action map are filtered out.
+ </description>
+ </method>
+ <method name="register_io_path">
+ <return type="void" />
+ <param index="0" name="interaction_profile" type="String" />
+ <param index="1" name="display_name" type="String" />
+ <param index="2" name="toplevel_path" type="String" />
+ <param index="3" name="openxr_path" type="String" />
+ <param index="4" name="openxr_extension_name" type="String" />
+ <param index="5" name="action_type" type="int" enum="OpenXRAction.ActionType" />
+ <description>
+ Registers an input/output path for the given [param interaction_profile]. The profile should previously have been registered using [method register_interaction_profile]. [param display_name] is the description shown to the user. [param toplevel_path] specifies the bind path this input/output can be bound to (e.g. [code]/user/hand/left[/code] or [code]/user/hand/right[/code]). [param openxr_path] is the action input/output being registered (e.g. [code]/user/hand/left/input/aim/pose[/code]). [param openxr_extension_name] restricts this input/output to an enabled/available extension, this doesn't need to repeat the extension on the profile but relates to overlapping extension (e.g. [code]XR_EXT_palm_pose[/code] that introduces [code]…/input/palm_ext/pose[/code] input paths). [param action_type] defines the type of input or output provided by OpenXR.
+ </description>
+ </method>
+ <method name="register_profile_rename">
+ <return type="void" />
+ <param index="0" name="old_name" type="String" />
+ <param index="1" name="new_name" type="String" />
+ <description>
+ Allows for renaming old interaction profile paths to new paths to maintain backwards compatibility with older action maps.
+ </description>
+ </method>
+ <method name="register_top_level_path">
+ <return type="void" />
+ <param index="0" name="display_name" type="String" />
+ <param index="1" name="openxr_path" type="String" />
+ <param index="2" name="openxr_extension_name" type="String" />
+ <description>
+ Registers a top level path to which profiles can be bound. For instance [code]/user/hand/left[/code] refers to the bind point for the player's left hand. Extensions can register additional top level paths, for instance a haptic vest extension might register [code]/user/body/vest[/code].
+ [param display_name] is the name shown to the user. [param openxr_path] is the top level path being registered. [param openxr_extension_name] is optional and ensures the top level path is only used if the specified extension is available/enabled.
+ When a top level path ends up being bound by OpenXR, a [XRPositionalTracker] is instantiated to manage the state of the device.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index f0ca649fe4..d0630626e6 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -23,6 +23,53 @@
Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized.
</description>
</method>
+ <method name="get_hand_joint_angular_velocity" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+ <description>
+ If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]!
+ </description>
+ </method>
+ <method name="get_hand_joint_linear_velocity" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+ <description>
+ If handtracking is enabled, returns the linear velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied!
+ </description>
+ </method>
+ <method name="get_hand_joint_position" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+ <description>
+ If handtracking is enabled, returns the position of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied!
+ </description>
+ </method>
+ <method name="get_hand_joint_radius" qualifiers="const">
+ <return type="float" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+ <description>
+ If handtracking is enabled, returns the radius of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is without worldscale applied!
+ </description>
+ </method>
+ <method name="get_hand_joint_rotation" qualifiers="const">
+ <return type="Quaternion" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+ <description>
+ 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_motion_range" qualifiers="const">
+ <return type="int" enum="OpenXRInterface.HandMotionRange" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <description>
+ If handtracking is enabled and motion range is supported, gets the currently configured motion range for [param hand].
+ </description>
+ </method>
<method name="is_action_set_active" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="String" />
@@ -30,6 +77,13 @@
Returns [code]true[/code] if the given action set is active.
</description>
</method>
+ <method name="is_foveation_supported" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXRs foveation extension is supported, the interface must be initialised before this returns a valid value.
+ [b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop.
+ </description>
+ </method>
<method name="set_action_set_active">
<return type="void" />
<param index="0" name="name" type="String" />
@@ -38,11 +92,25 @@
Sets the given action set as active or inactive.
</description>
</method>
+ <method name="set_motion_range">
+ <return type="void" />
+ <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+ <param index="1" name="motion_range" type="int" enum="OpenXRInterface.HandMotionRange" />
+ <description>
+ If handtracking is enabled and motion range is supported, sets the currently configured motion range for [param hand] to [param motion_range].
+ </description>
+ </method>
</methods>
<members>
<member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0">
The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized.
</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 initialised before this is accessible. If enabled foveation will automatically adjusted between low and [member foveation_level].
+ </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 initialised before this is accessible.
+ </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.
</member>
@@ -74,4 +142,102 @@
</description>
</signal>
</signals>
+ <constants>
+ <constant name="HAND_LEFT" value="0" enum="Hand">
+ Left hand.
+ </constant>
+ <constant name="HAND_RIGHT" value="1" enum="Hand">
+ Right hand.
+ </constant>
+ <constant name="HAND_MAX" value="2" enum="Hand">
+ Maximum value for the hand enum.
+ </constant>
+ <constant name="HAND_MOTION_RANGE_UNOBSTRUCTED" value="0" enum="HandMotionRange">
+ </constant>
+ <constant name="HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER" value="1" enum="HandMotionRange">
+ </constant>
+ <constant name="HAND_MOTION_RANGE_MAX" value="2" enum="HandMotionRange">
+ </constant>
+ <constant name="HAND_JOINT_PALM" value="0" enum="HandJoints">
+ Palm joint.
+ </constant>
+ <constant name="HAND_JOINT_WRIST" value="1" enum="HandJoints">
+ Wrist joint.
+ </constant>
+ <constant name="HAND_JOINT_THUMB_METACARPAL" value="2" enum="HandJoints">
+ Thumb metacarpal joint.
+ </constant>
+ <constant name="HAND_JOINT_THUMB_PROXIMAL" value="3" enum="HandJoints">
+ Thumb proximal joint.
+ </constant>
+ <constant name="HAND_JOINT_THUMB_DISTAL" value="4" enum="HandJoints">
+ Thumb distal joint.
+ </constant>
+ <constant name="HAND_JOINT_THUMB_TIP" value="5" enum="HandJoints">
+ Thumb tip joint.
+ </constant>
+ <constant name="HAND_JOINT_INDEX_METACARPAL" value="6" enum="HandJoints">
+ Index metacarpal joint.
+ </constant>
+ <constant name="HAND_JOINT_INDEX_PROXIMAL" value="7" enum="HandJoints">
+ Index proximal joint.
+ </constant>
+ <constant name="HAND_JOINT_INDEX_INTERMEDIATE" value="8" enum="HandJoints">
+ Index intermediate joint.
+ </constant>
+ <constant name="HAND_JOINT_INDEX_DISTAL" value="9" enum="HandJoints">
+ Index distal joint.
+ </constant>
+ <constant name="HAND_JOINT_INDEX_TIP" value="10" enum="HandJoints">
+ Index tip joint.
+ </constant>
+ <constant name="HAND_JOINT_MIDDLE_METACARPAL" value="11" enum="HandJoints">
+ Middle metacarpal joint.
+ </constant>
+ <constant name="HAND_JOINT_MIDDLE_PROXIMAL" value="12" enum="HandJoints">
+ Middle proximal joint.
+ </constant>
+ <constant name="HAND_JOINT_MIDDLE_INTERMEDIATE" value="13" enum="HandJoints">
+ Middle intermediate joint.
+ </constant>
+ <constant name="HAND_JOINT_MIDDLE_DISTAL" value="14" enum="HandJoints">
+ Middle distal joint.
+ </constant>
+ <constant name="HAND_JOINT_MIDDLE_TIP" value="15" enum="HandJoints">
+ Middle tip joint.
+ </constant>
+ <constant name="HAND_JOINT_RING_METACARPAL" value="16" enum="HandJoints">
+ Ring metacarpal joint.
+ </constant>
+ <constant name="HAND_JOINT_RING_PROXIMAL" value="17" enum="HandJoints">
+ Ring proximal joint.
+ </constant>
+ <constant name="HAND_JOINT_RING_INTERMEDIATE" value="18" enum="HandJoints">
+ Ring intermediate joint.
+ </constant>
+ <constant name="HAND_JOINT_RING_DISTAL" value="19" enum="HandJoints">
+ Ring distal joint.
+ </constant>
+ <constant name="HAND_JOINT_RING_TIP" value="20" enum="HandJoints">
+ Ring tip joint.
+ </constant>
+ <constant name="HAND_JOINT_LITTLE_METACARPAL" value="21" enum="HandJoints">
+ Little metacarpal joint.
+ </constant>
+ <constant name="HAND_JOINT_LITTLE_PROXIMAL" value="22" enum="HandJoints">
+ Little proximal joint.
+ </constant>
+ <constant name="HAND_JOINT_LITTLE_INTERMEDIATE" value="23" enum="HandJoints">
+ Little intermediate joint.
+ </constant>
+ <constant name="HAND_JOINT_LITTLE_DISTAL" value="24" enum="HandJoints">
+ Little distal joint.
+ </constant>
+ <constant name="HAND_JOINT_LITTLE_TIP" value="25" enum="HandJoints">
+ Little tip joint.
+ </constant>
+ <constant name="HAND_JOINT_MAX" value="26" enum="HandJoints">
+ Maximum value for the hand joint enum.
+ </constant>
+ </constants>
</class>
diff --git a/modules/openxr/editor/openxr_action_editor.cpp b/modules/openxr/editor/openxr_action_editor.cpp
index 586b0b0697..4b188471a0 100644
--- a/modules/openxr/editor/openxr_action_editor.cpp
+++ b/modules/openxr/editor/openxr_action_editor.cpp
@@ -30,6 +30,8 @@
#include "openxr_action_editor.h"
+#include "editor/editor_string_names.h"
+
void OpenXRActionEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_do_set_name", "name"), &OpenXRActionEditor::_do_set_name);
ClassDB::bind_method(D_METHOD("_do_set_localized_name", "name"), &OpenXRActionEditor::_do_set_localized_name);
@@ -39,7 +41,7 @@ void OpenXRActionEditor::_bind_methods() {
}
void OpenXRActionEditor::_theme_changed() {
- rem_action->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ rem_action->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
}
void OpenXRActionEditor::_notification(int p_what) {
diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp
index 6a63720257..a9fc6c4db6 100644
--- a/modules/openxr/editor/openxr_action_set_editor.cpp
+++ b/modules/openxr/editor/openxr_action_set_editor.cpp
@@ -30,6 +30,7 @@
#include "openxr_action_set_editor.h"
+#include "editor/editor_string_names.h"
#include "openxr_action_editor.h"
void OpenXRActionSetEditor::_bind_methods() {
@@ -45,16 +46,16 @@ void OpenXRActionSetEditor::_bind_methods() {
void OpenXRActionSetEditor::_set_fold_icon() {
if (is_expanded) {
- fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons")));
+ fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), EditorStringName(EditorIcons)));
} else {
- fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons")));
+ fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons)));
}
}
void OpenXRActionSetEditor::_theme_changed() {
_set_fold_icon();
- add_action->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- rem_action_set->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ add_action->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ rem_action_set->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
}
void OpenXRActionSetEditor::_notification(int p_what) {
diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
index 1f63399ee7..7bccabf936 100644
--- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp
+++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
@@ -30,6 +30,7 @@
#include "openxr_interaction_profile_editor.h"
+#include "editor/editor_string_names.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
@@ -148,7 +149,7 @@ OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase(Ref<OpenX
String profile_path = interaction_profile->get_interaction_profile_path();
String profile_name = profile_path;
- profile_def = OpenXRInteractionProfileMetaData::get_singleton()->get_profile(profile_path);
+ profile_def = OpenXRInteractionProfileMetadata::get_singleton()->get_profile(profile_path);
if (profile_def != nullptr) {
profile_name = profile_def->display_name;
}
@@ -185,7 +186,7 @@ void OpenXRInteractionProfileEditor::_on_remove_pressed(const String p_action, c
undo_redo->commit_action(true);
}
-void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, const OpenXRInteractionProfileMetaData::IOPath *p_io_path) {
+void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, const OpenXRInteractionProfileMetadata::IOPath *p_io_path) {
HBoxContainer *path_hb = memnew(HBoxContainer);
path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
p_container->add_child(path_hb);
@@ -220,7 +221,7 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
path_hb->add_child(type_label);
Button *path_add = memnew(Button);
- path_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ path_add->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
path_add->set_flat(true);
path_add->connect("pressed", callable_mp(this, &OpenXRInteractionProfileEditor::select_action_for).bind(String(p_io_path->openxr_path)));
path_hb->add_child(path_add);
@@ -248,7 +249,7 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
Button *action_rem = memnew(Button);
action_rem->set_flat(true);
- action_rem->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ action_rem->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
action_rem->connect("pressed", callable_mp((OpenXRInteractionProfileEditor *)this, &OpenXRInteractionProfileEditor::_on_remove_pressed).bind(action->get_name_with_set(), String(p_io_path->openxr_path)));
action_hb->add_child(action_rem);
}
@@ -274,7 +275,7 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() {
// Determine toplevel paths
Vector<String> top_level_paths;
for (int i = 0; i < profile_def->io_paths.size(); i++) {
- const OpenXRInteractionProfileMetaData::IOPath *io_path = &profile_def->io_paths[i];
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = &profile_def->io_paths[i];
if (!top_level_paths.has(io_path->top_level_path)) {
top_level_paths.push_back(io_path->top_level_path);
@@ -291,11 +292,11 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() {
panel->add_child(container);
Label *label = memnew(Label);
- label->set_text(OpenXRInteractionProfileMetaData::get_singleton()->get_top_level_name(top_level_paths[i]));
+ label->set_text(OpenXRInteractionProfileMetadata::get_singleton()->get_top_level_name(top_level_paths[i]));
container->add_child(label);
for (int j = 0; j < profile_def->io_paths.size(); j++) {
- const OpenXRInteractionProfileMetaData::IOPath *io_path = &profile_def->io_paths[j];
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = &profile_def->io_paths[j];
if (io_path->top_level_path == top_level_paths[i]) {
_add_io_path(container, io_path);
}
diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.h b/modules/openxr/editor/openxr_interaction_profile_editor.h
index b9b3fbd81d..2ec72127cf 100644
--- a/modules/openxr/editor/openxr_interaction_profile_editor.h
+++ b/modules/openxr/editor/openxr_interaction_profile_editor.h
@@ -33,7 +33,7 @@
#include "../action_map/openxr_action_map.h"
#include "../action_map/openxr_interaction_profile.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
#include "openxr_select_action_dialog.h"
#include "editor/editor_undo_redo_manager.h"
@@ -52,7 +52,7 @@ protected:
static void _bind_methods();
void _notification(int p_what);
- const OpenXRInteractionProfileMetaData::InteractionProfile *profile_def = nullptr;
+ const OpenXRInteractionProfileMetadata::InteractionProfile *profile_def = nullptr;
public:
Ref<OpenXRInteractionProfile> get_interaction_profile() { return interaction_profile; }
@@ -77,7 +77,7 @@ private:
HBoxContainer *main_hb = nullptr;
OpenXRSelectActionDialog *select_action_dialog = nullptr;
- void _add_io_path(VBoxContainer *p_container, const OpenXRInteractionProfileMetaData::IOPath *p_io_path);
+ void _add_io_path(VBoxContainer *p_container, const OpenXRInteractionProfileMetadata::IOPath *p_io_path);
public:
void select_action_for(const String p_io_path);
diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
index 9362c08174..08fcdaf771 100644
--- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
+++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp
@@ -75,13 +75,13 @@ void OpenXRSelectInteractionProfileDialog::open(PackedStringArray p_do_not_inclu
ip_buttons.clear();
// in with the new
- PackedStringArray interaction_profiles = OpenXRInteractionProfileMetaData::get_singleton()->get_interaction_profile_paths();
+ PackedStringArray interaction_profiles = OpenXRInteractionProfileMetadata::get_singleton()->get_interaction_profile_paths();
for (int i = 0; i < interaction_profiles.size(); i++) {
String path = interaction_profiles[i];
if (!p_do_not_include.has(path)) {
Button *ip_button = memnew(Button);
ip_button->set_flat(true);
- ip_button->set_text(OpenXRInteractionProfileMetaData::get_singleton()->get_profile(path)->display_name);
+ ip_button->set_text(OpenXRInteractionProfileMetadata::get_singleton()->get_profile(path)->display_name);
ip_button->connect("pressed", callable_mp(this, &OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile).bind(path));
main_vb->add_child(ip_button);
diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.h b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h
index aae0368f67..1d1615321c 100644
--- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.h
+++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h
@@ -31,7 +31,7 @@
#ifndef OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H
#define OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 81ba9c56b8..4829f713d2 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -126,7 +126,7 @@ void OpenXRExtensionWrapperExtension::on_before_instance_created() {
}
void OpenXRExtensionWrapperExtension::on_instance_created(const XrInstance p_instance) {
- uint64_t instance = reinterpret_cast<uint64_t>(p_instance);
+ uint64_t instance = (uint64_t)p_instance;
GDVIRTUAL_CALL(_on_instance_created, instance);
}
@@ -135,7 +135,7 @@ void OpenXRExtensionWrapperExtension::on_instance_destroyed() {
}
void OpenXRExtensionWrapperExtension::on_session_created(const XrSession p_session) {
- uint64_t session = reinterpret_cast<uint64_t>(p_session);
+ uint64_t session = (uint64_t)p_session;
GDVIRTUAL_CALL(_on_session_created, session);
}
diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp
new file mode 100644
index 0000000000..7277f85b12
--- /dev/null
+++ b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp
@@ -0,0 +1,168 @@
+/**************************************************************************/
+/* openxr_fb_foveation_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_fb_foveation_extension.h"
+#include "core/config/project_settings.h"
+
+OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::singleton = nullptr;
+
+OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRFBFoveationExtension::OpenXRFBFoveationExtension(const String &p_rendering_driver) {
+ singleton = this;
+ rendering_driver = p_rendering_driver;
+ swapchain_update_state_ext = OpenXRFBUpdateSwapchainExtension::get_singleton();
+ int fov_level = GLOBAL_GET("xr/openxr/foveation_level");
+ if (fov_level >= 0 && fov_level < 4) {
+ foveation_level = XrFoveationLevelFB(fov_level);
+ }
+ bool fov_dyn = GLOBAL_GET("xr/openxr/foveation_dynamic");
+ foveation_dynamic = fov_dyn ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB;
+
+ swapchain_create_info_foveation_fb.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB;
+ swapchain_create_info_foveation_fb.next = nullptr;
+ swapchain_create_info_foveation_fb.flags = 0;
+}
+
+OpenXRFBFoveationExtension::~OpenXRFBFoveationExtension() {
+ singleton = nullptr;
+ swapchain_update_state_ext = nullptr;
+}
+
+HashMap<String, bool *> OpenXRFBFoveationExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ if (rendering_driver == "vulkan") {
+ // This is currently only supported on OpenGL, but we may add Vulkan support in the future...
+
+ } else if (rendering_driver == "opengl3") {
+ request_extensions[XR_FB_FOVEATION_EXTENSION_NAME] = &fb_foveation_ext;
+ request_extensions[XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME] = &fb_foveation_configuration_ext;
+ }
+
+ return request_extensions;
+}
+
+void OpenXRFBFoveationExtension::on_instance_created(const XrInstance p_instance) {
+ if (fb_foveation_ext) {
+ EXT_INIT_XR_FUNC(xrCreateFoveationProfileFB);
+ EXT_INIT_XR_FUNC(xrDestroyFoveationProfileFB);
+ }
+
+ if (fb_foveation_configuration_ext) {
+ // nothing to register here...
+ }
+}
+
+void OpenXRFBFoveationExtension::on_instance_destroyed() {
+ fb_foveation_ext = false;
+ fb_foveation_configuration_ext = false;
+}
+
+bool OpenXRFBFoveationExtension::is_enabled() const {
+ return swapchain_update_state_ext != nullptr && swapchain_update_state_ext->is_enabled() && fb_foveation_ext && fb_foveation_configuration_ext;
+}
+
+void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) {
+ if (is_enabled()) {
+ swapchain_create_info_foveation_fb.next = p_next_pointer;
+ return &swapchain_create_info_foveation_fb;
+ } else {
+ return p_next_pointer;
+ }
+}
+
+void OpenXRFBFoveationExtension::on_state_ready() {
+ update_profile();
+}
+
+XrFoveationLevelFB OpenXRFBFoveationExtension::get_foveation_level() const {
+ return foveation_level;
+}
+
+void OpenXRFBFoveationExtension::set_foveation_level(XrFoveationLevelFB p_foveation_level) {
+ foveation_level = p_foveation_level;
+
+ // Update profile will do nothing if we're not yet initialised
+ update_profile();
+}
+
+XrFoveationDynamicFB OpenXRFBFoveationExtension::get_foveation_dynamic() const {
+ return foveation_dynamic;
+}
+
+void OpenXRFBFoveationExtension::set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic) {
+ foveation_dynamic = p_foveation_dynamic;
+
+ // Update profile will do nothing if we're not yet initialised
+ update_profile();
+}
+
+void OpenXRFBFoveationExtension::update_profile() {
+ if (!is_enabled()) {
+ return;
+ }
+
+ XrFoveationLevelProfileCreateInfoFB level_profile_create_info;
+ level_profile_create_info.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB;
+ level_profile_create_info.next = nullptr;
+ level_profile_create_info.level = foveation_level;
+ level_profile_create_info.verticalOffset = 0.0f;
+ level_profile_create_info.dynamic = foveation_dynamic;
+
+ XrFoveationProfileCreateInfoFB profile_create_info;
+ profile_create_info.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB;
+ profile_create_info.next = &level_profile_create_info;
+
+ XrFoveationProfileFB foveation_profile;
+ XrResult result = xrCreateFoveationProfileFB(OpenXRAPI::get_singleton()->get_session(), &profile_create_info, &foveation_profile);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Unable to create the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]");
+ return;
+ }
+
+ XrSwapchainStateFoveationFB foveation_update_state;
+ foveation_update_state.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB;
+ foveation_update_state.profile = foveation_profile;
+
+ result = swapchain_update_state_ext->xrUpdateSwapchainFB(OpenXRAPI::get_singleton()->get_color_swapchain(), (XrSwapchainStateBaseHeaderFB *)&foveation_update_state);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Unable to update the swapchain [", OpenXRAPI::get_singleton()->get_error_string(result), "]");
+
+ // We still want to destroy our profile so keep going...
+ }
+
+ result = xrDestroyFoveationProfileFB(foveation_profile);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Unable to destroy the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]");
+ }
+}
diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.h b/modules/openxr/extensions/openxr_fb_foveation_extension.h
new file mode 100644
index 0000000000..1c5e722731
--- /dev/null
+++ b/modules/openxr/extensions/openxr_fb_foveation_extension.h
@@ -0,0 +1,96 @@
+/**************************************************************************/
+/* openxr_fb_foveation_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_FB_FOVEATION_EXTENSION_H
+#define OPENXR_FB_FOVEATION_EXTENSION_H
+
+// This extension implements the FB Foveation extension.
+// This is an extension Meta added due to VRS being unavailable on Android.
+// Other Android based devices are implementing this as well, see:
+// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_foveation
+
+// Note: Currently we only support this for OpenGL.
+// This extension works on enabling foveated rendering on the swapchain.
+// Vulkan does not render 3D content directly to the swapchain image
+// hence this extension can't be used.
+
+#include "../openxr_api.h"
+#include "../util.h"
+#include "openxr_extension_wrapper.h"
+#include "openxr_fb_update_swapchain_extension.h"
+
+class OpenXRFBFoveationExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRFBFoveationExtension *get_singleton();
+
+ OpenXRFBFoveationExtension(const String &p_rendering_driver);
+ virtual ~OpenXRFBFoveationExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ virtual void on_instance_created(const XrInstance p_instance) override;
+ virtual void on_instance_destroyed() override;
+
+ virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override;
+
+ virtual void on_state_ready() override;
+
+ bool is_enabled() const;
+
+ XrFoveationLevelFB get_foveation_level() const;
+ void set_foveation_level(XrFoveationLevelFB p_foveation_level);
+
+ XrFoveationDynamicFB get_foveation_dynamic() const;
+ void set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic);
+
+private:
+ static OpenXRFBFoveationExtension *singleton;
+
+ // Setup
+ String rendering_driver;
+ bool fb_foveation_ext = false;
+ bool fb_foveation_configuration_ext = false;
+
+ // Configuration
+ XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB;
+ XrFoveationDynamicFB foveation_dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB;
+
+ void update_profile();
+
+ // Enable foveation on this swapchain
+ XrSwapchainCreateInfoFoveationFB swapchain_create_info_foveation_fb;
+ OpenXRFBUpdateSwapchainExtension *swapchain_update_state_ext = nullptr;
+
+ // OpenXR API call wrappers
+ EXT_PROTO_XRRESULT_FUNC3(xrCreateFoveationProfileFB, (XrSession), session, (const XrFoveationProfileCreateInfoFB *), create_info, (XrFoveationProfileFB *), profile);
+ EXT_PROTO_XRRESULT_FUNC1(xrDestroyFoveationProfileFB, (XrFoveationProfileFB), profile);
+};
+
+#endif // OPENXR_FB_FOVEATION_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp
index f730f2bd2c..3da0ffd9c7 100644
--- a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp
+++ b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp
@@ -71,7 +71,7 @@ Viewport *OpenXRFbPassthroughExtensionWrapper::get_main_viewport() {
return nullptr;
}
- auto *scene_tree = Object::cast_to<SceneTree>(main_loop);
+ SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop);
if (!scene_tree) {
print_error("Unable to retrieve scene tree");
return nullptr;
diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
new file mode 100644
index 0000000000..1289183ea4
--- /dev/null
+++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
@@ -0,0 +1,102 @@
+/**************************************************************************/
+/* openxr_fb_update_swapchain_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_fb_update_swapchain_extension.h"
+
+// always include this as late as possible
+#include "../openxr_platform_inc.h"
+
+OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr;
+
+OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRFBUpdateSwapchainExtension::OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver) {
+ singleton = this;
+ rendering_driver = p_rendering_driver;
+}
+
+OpenXRFBUpdateSwapchainExtension::~OpenXRFBUpdateSwapchainExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRFBUpdateSwapchainExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME] = &fb_swapchain_update_state_ext;
+
+ if (rendering_driver == "vulkan") {
+#ifdef XR_USE_GRAPHICS_API_VULKAN
+ request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME] = &fb_swapchain_update_state_vulkan_ext;
+#endif
+ } else if (rendering_driver == "opengl3") {
+#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
+ request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME] = &fb_swapchain_update_state_opengles_ext;
+#endif
+ }
+
+ return request_extensions;
+}
+
+void OpenXRFBUpdateSwapchainExtension::on_instance_created(const XrInstance p_instance) {
+ if (fb_swapchain_update_state_ext) {
+ EXT_INIT_XR_FUNC(xrUpdateSwapchainFB);
+ EXT_INIT_XR_FUNC(xrGetSwapchainStateFB);
+ }
+
+ if (fb_swapchain_update_state_vulkan_ext) {
+ // nothing to register here...
+ }
+
+ if (fb_swapchain_update_state_opengles_ext) {
+ // nothing to register here...
+ }
+}
+
+void OpenXRFBUpdateSwapchainExtension::on_instance_destroyed() {
+ fb_swapchain_update_state_ext = false;
+ fb_swapchain_update_state_vulkan_ext = false;
+ fb_swapchain_update_state_opengles_ext = false;
+}
+
+bool OpenXRFBUpdateSwapchainExtension::is_enabled() const {
+ if (rendering_driver == "vulkan") {
+ return fb_swapchain_update_state_ext && fb_swapchain_update_state_vulkan_ext;
+ } else if (rendering_driver == "opengl3") {
+#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
+ return fb_swapchain_update_state_ext && fb_swapchain_update_state_opengles_ext;
+#else
+ return fb_swapchain_update_state_ext;
+#endif
+ }
+
+ return false;
+}
diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h
new file mode 100644
index 0000000000..a02b550e58
--- /dev/null
+++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h
@@ -0,0 +1,73 @@
+/**************************************************************************/
+/* openxr_fb_update_swapchain_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_FB_UPDATE_SWAPCHAIN_EXTENSION_H
+#define OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H
+
+// This extension implements the FB update swapchain extension.
+// This is an extension Meta added to further configure the swapchain.
+// Other Android based devices are implementing this as well, see:
+// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_swapchain_update_state
+
+#include "../openxr_api.h"
+#include "../util.h"
+#include "openxr_extension_wrapper.h"
+
+class OpenXRFBUpdateSwapchainExtension : public OpenXRExtensionWrapper {
+ friend class OpenXRFBFoveationExtension;
+
+public:
+ static OpenXRFBUpdateSwapchainExtension *get_singleton();
+
+ OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver);
+ virtual ~OpenXRFBUpdateSwapchainExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ virtual void on_instance_created(const XrInstance p_instance) override;
+ virtual void on_instance_destroyed() override;
+
+ bool is_enabled() const;
+
+private:
+ static OpenXRFBUpdateSwapchainExtension *singleton;
+
+ // Setup
+ String rendering_driver;
+ bool fb_swapchain_update_state_ext = false;
+ bool fb_swapchain_update_state_vulkan_ext = false;
+ bool fb_swapchain_update_state_opengles_ext = false;
+
+ // OpenXR API call wrappers
+ EXT_PROTO_XRRESULT_FUNC2(xrUpdateSwapchainFB, (XrSwapchain), swapchain, (const XrSwapchainStateBaseHeaderFB *), state);
+ EXT_PROTO_XRRESULT_FUNC2(xrGetSwapchainStateFB, (XrSwapchain), swapchain, (XrSwapchainStateBaseHeaderFB *), state);
+};
+
+#endif // OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index 65559afed0..c92b2b08d0 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -276,3 +276,62 @@ void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJoints
ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS);
hand_trackers[p_hand].motion_range = p_motion_range;
}
+
+Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Quaternion());
+ ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion());
+
+ if (!hand_trackers[p_hand].is_initialized) {
+ return Quaternion();
+ }
+
+ const XrHandJointLocationEXT &location = hand_trackers[p_hand].joint_locations[p_joint];
+ return Quaternion(location.pose.orientation.x, location.pose.orientation.y, location.pose.orientation.z, location.pose.orientation.w);
+}
+
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+ ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
+
+ if (!hand_trackers[p_hand].is_initialized) {
+ return Vector3();
+ }
+
+ const XrHandJointLocationEXT &location = hand_trackers[p_hand].joint_locations[p_joint];
+ return Vector3(location.pose.position.x, location.pose.position.y, location.pose.position.z);
+}
+
+float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, 0.0);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, 0.0);
+
+ if (!hand_trackers[p_hand].is_initialized) {
+ return 0.0;
+ }
+
+ return hand_trackers[p_hand].joint_locations[p_joint].radius;
+}
+
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+ ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
+
+ if (!hand_trackers[p_hand].is_initialized) {
+ return Vector3();
+ }
+
+ const XrHandJointVelocityEXT &velocity = hand_trackers[p_hand].joint_velocities[p_joint];
+ return Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z);
+}
+
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+ ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
+
+ if (!hand_trackers[p_hand].is_initialized) {
+ return Vector3();
+ }
+
+ const XrHandJointVelocityEXT &velocity = hand_trackers[p_hand].joint_velocities[p_joint];
+ return Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z);
+}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index e86831e71b..99d315c525 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -32,6 +32,7 @@
#define OPENXR_HAND_TRACKING_EXTENSION_H
#include "../util.h"
+#include "core/math/quaternion.h"
#include "openxr_extension_wrapper.h"
#define MAX_OPENXR_TRACKED_HANDS 2
@@ -73,6 +74,13 @@ public:
XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const;
void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range);
+ Quaternion get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ float get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const;
+
+ Vector3 get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
+
private:
static OpenXRHandTrackingExtension *singleton;
diff --git a/modules/openxr/extensions/openxr_htc_controller_extension.cpp b/modules/openxr/extensions/openxr_htc_controller_extension.cpp
index 51364d83d8..c4ecb4e57c 100644
--- a/modules/openxr/extensions/openxr_htc_controller_extension.cpp
+++ b/modules/openxr/extensions/openxr_htc_controller_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_htc_controller_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
HashMap<String, bool *> OpenXRHTCControllerExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -46,7 +46,7 @@ bool OpenXRHTCControllerExtension::is_available(HTCControllers p_type) {
}
void OpenXRHTCControllerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
// HTC Vive Cosmos controller
diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
index 087631a8b9..8b8c6c5353 100644
--- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
+++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_htc_vive_tracker_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
#include "core/string/print_string.h"
@@ -47,7 +47,7 @@ bool OpenXRHTCViveTrackerExtension::is_available() {
}
void OpenXRHTCViveTrackerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
// HTC Vive tracker
diff --git a/modules/openxr/extensions/openxr_huawei_controller_extension.cpp b/modules/openxr/extensions/openxr_huawei_controller_extension.cpp
index 8b8662cfcb..baa6d6ded8 100644
--- a/modules/openxr/extensions/openxr_huawei_controller_extension.cpp
+++ b/modules/openxr/extensions/openxr_huawei_controller_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_huawei_controller_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
HashMap<String, bool *> OpenXRHuaweiControllerExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -45,7 +45,7 @@ bool OpenXRHuaweiControllerExtension::is_available() {
}
void OpenXRHuaweiControllerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
// Huawei controller
diff --git a/modules/openxr/extensions/openxr_ml2_controller_extension.cpp b/modules/openxr/extensions/openxr_ml2_controller_extension.cpp
index 7dad8deb8c..979ac22d08 100644
--- a/modules/openxr/extensions/openxr_ml2_controller_extension.cpp
+++ b/modules/openxr/extensions/openxr_ml2_controller_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_ml2_controller_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
HashMap<String, bool *> OpenXRML2ControllerExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -45,7 +45,7 @@ bool OpenXRML2ControllerExtension::is_available() {
}
void OpenXRML2ControllerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
// Magic Leap 2 Controller
diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h
index 598d3415ad..3b0aa0bce9 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/openxr_opengl_extension.h
@@ -39,39 +39,8 @@
#include "core/templates/vector.h"
-#ifdef ANDROID_ENABLED
-#define XR_USE_GRAPHICS_API_OPENGL_ES
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#else
-#define XR_USE_GRAPHICS_API_OPENGL
-#endif
-
-#ifdef WINDOWS_ENABLED
-// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform
-// however due to the way the openxr headers are put together, we have no choice.
-#include <windows.h>
-#endif
-
-#ifdef X11_ENABLED
-#include OPENGL_INCLUDE_H
-#define GL_GLEXT_PROTOTYPES 1
-#define GL3_PROTOTYPES 1
-#include "thirdparty/glad/glad/gl.h"
-#include "thirdparty/glad/glad/glx.h"
-
-#include <X11/Xlib.h>
-#endif
-
-#ifdef ANDROID_ENABLED
-// The jobject type from jni.h is used by openxr_platform.h on Android.
-#include <jni.h>
-#endif
-
-// Include platform dependent structs.
-#include <openxr/openxr_platform.h>
+// always include this as late as possible
+#include "../openxr_platform_inc.h"
class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper {
public:
diff --git a/modules/openxr/extensions/openxr_pico_controller_extension.cpp b/modules/openxr/extensions/openxr_pico_controller_extension.cpp
index 8be961a68e..f2901d49f7 100644
--- a/modules/openxr/extensions/openxr_pico_controller_extension.cpp
+++ b/modules/openxr/extensions/openxr_pico_controller_extension.cpp
@@ -30,20 +30,16 @@
#include "openxr_pico_controller_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
-
-// Pico controllers are not part of the OpenXR spec at the time of writing this
-// code. We'll hardcode the extension name that is used internally, verified by
-// tests on the Pico 4. Note that later versions of the Pico 4 and OpenXR
-// runtime on Pico might use a different, standardized extension name.
-#ifndef XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME
-#define XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_PICO_controller_interaction"
-#endif
+#include "../action_map/openxr_interaction_profile_metadata.h"
HashMap<String, bool *> OpenXRPicoControllerExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
- request_extensions[XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available;
+ // Note, this used to be XR_PICO_controller_interaction but that has since been retired
+ // and was never part of the OpenXX specification.
+ // All PICO devices should be updated to an OS supporting the official extension.
+
+ request_extensions[XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available;
return request_extensions;
}
@@ -53,47 +49,91 @@ bool OpenXRPicoControllerExtension::is_available() {
}
void OpenXRPicoControllerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
- // Pico controller (Pico 4 and Pico Neo 3 controllers)
- metadata->register_interaction_profile("Pico controller", "/interaction_profiles/pico/neo3_controller", XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "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/pico/neo3_controller", "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/pico/neo3_controller", "Menu click", "/user/hand/left", "/user/hand/left/input/back/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Screenshot click", "/user/hand/right", "/user/hand/right/input/back/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
-
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
-
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
-
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
-
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
-
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
- metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ // Make sure we switch to our new name.
+ metadata->register_profile_rename("/interaction_profiles/pico/neo3_controller", "/interaction_profiles/bytedance/pico_neo3_controller");
+
+ // Pico neo 3 controller.
+ metadata->register_interaction_profile("Pico Neo3 controller", "/interaction_profiles/bytedance/pico_neo3_controller", XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "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/bytedance/pico_neo3_controller", "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/bytedance/pico_neo3_controller", "Menu click", "/user/hand/left", "/user/hand/left/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Menu click", "/user/hand/right", "/user/hand/right/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico_neo3_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+
+ // Pico 4 controller.
+ metadata->register_interaction_profile("Pico 4 controller", "/interaction_profiles/bytedance/pico4_controller", XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "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/bytedance/pico4_controller", "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/bytedance/pico4_controller", "Menu click", "/user/hand/left", "/user/hand/left/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ // Note, no menu on right controller!
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/bytedance/pico4_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
}
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h
index 4add6f6fa2..f31621fda0 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/openxr_vulkan_extension.h
@@ -36,24 +36,9 @@
#include "openxr_extension_wrapper.h"
#include "core/templates/vector.h"
-#include "drivers/vulkan/vulkan_context.h"
-// Need to include Vulkan so we know of type definitions.
-#define XR_USE_GRAPHICS_API_VULKAN
-
-#ifdef WINDOWS_ENABLED
-// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform
-// however due to the way the openxr headers are put together, we have no choice.
-#include <windows.h>
-#endif
-
-#ifdef ANDROID_ENABLED
-// The jobject type from jni.h is used by openxr_platform.h on Android.
-#include <jni.h>
-#endif
-
-// Include platform dependent structs.
-#include <openxr/openxr_platform.h>
+// always include this as late as possible
+#include "../openxr_platform_inc.h"
class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks {
public:
diff --git a/modules/openxr/extensions/openxr_wmr_controller_extension.cpp b/modules/openxr/extensions/openxr_wmr_controller_extension.cpp
index a9b27013f1..7d43d2e304 100644
--- a/modules/openxr/extensions/openxr_wmr_controller_extension.cpp
+++ b/modules/openxr/extensions/openxr_wmr_controller_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_wmr_controller_extension.h"
-#include "../action_map/openxr_interaction_profile_meta_data.h"
+#include "../action_map/openxr_interaction_profile_metadata.h"
HashMap<String, bool *> OpenXRWMRControllerExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -47,7 +47,7 @@ bool OpenXRWMRControllerExtension::is_available(WMRControllers p_type) {
}
void OpenXRWMRControllerExtension::on_register_metadata() {
- OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
ERR_FAIL_NULL(metadata);
// HP MR controller (newer G2 controllers)
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 91684b55b9..b1c7ab1615 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -43,31 +43,7 @@
#include "editor/editor_settings.h"
#endif
-// We need to have all the graphics API defines before the Vulkan or OpenGL
-// extensions are included, otherwise we'll only get one graphics API.
-#ifdef VULKAN_ENABLED
-#define XR_USE_GRAPHICS_API_VULKAN
-#endif
-#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
-#ifdef ANDROID_ENABLED
-#define XR_USE_GRAPHICS_API_OPENGL_ES
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#else
-#define XR_USE_GRAPHICS_API_OPENGL
-#endif // ANDROID_ENABLED
-#ifdef X11_ENABLED
-#include OPENGL_INCLUDE_H
-#define GL_GLEXT_PROTOTYPES 1
-#define GL3_PROTOTYPES 1
-#include "thirdparty/glad/glad/gl.h"
-#include "thirdparty/glad/glad/glx.h"
-
-#include <X11/Xlib.h>
-#endif // X11_ENABLED
-#endif // GLES_ENABLED
+#include "openxr_platform_inc.h"
#ifdef VULKAN_ENABLED
#include "extensions/openxr_vulkan_extension.h"
@@ -79,7 +55,9 @@
#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
+#include "extensions/openxr_fb_foveation_extension.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
+#include "extensions/openxr_fb_update_swapchain_extension.h"
#ifdef ANDROID_ENABLED
#define OPENXR_LOADER_NAME "libopenxr_loader.so"
@@ -216,7 +194,7 @@ bool OpenXRAPI::is_extension_enabled(const String &p_extension) const {
}
bool OpenXRAPI::is_top_level_path_supported(const String &p_toplevel_path) {
- String required_extension = OpenXRInteractionProfileMetaData::get_singleton()->get_top_level_extension(p_toplevel_path);
+ String required_extension = OpenXRInteractionProfileMetadata::get_singleton()->get_top_level_extension(p_toplevel_path);
// If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error.
ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported toplevel path " + p_toplevel_path);
@@ -236,7 +214,7 @@ bool OpenXRAPI::is_top_level_path_supported(const String &p_toplevel_path) {
}
bool OpenXRAPI::is_interaction_profile_supported(const String &p_ip_path) {
- String required_extension = OpenXRInteractionProfileMetaData::get_singleton()->get_interaction_profile_extension(p_ip_path);
+ String required_extension = OpenXRInteractionProfileMetadata::get_singleton()->get_interaction_profile_extension(p_ip_path);
// If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error.
ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported interaction profile " + p_ip_path);
@@ -260,9 +238,9 @@ bool OpenXRAPI::interaction_profile_supports_io_path(const String &p_ip_path, co
return false;
}
- const OpenXRInteractionProfileMetaData::IOPath *io_path = OpenXRInteractionProfileMetaData::get_singleton()->get_io_path(p_ip_path, p_io_path);
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = OpenXRInteractionProfileMetadata::get_singleton()->get_io_path(p_ip_path, p_io_path);
- // If the io_path is not part of our meta data we've likely got a misspelled name or a bad action map, report
+ // If the io_path is not part of our metadata we've likely got a misspelled name or a bad action map, report
ERR_FAIL_NULL_V_MSG(io_path, false, "OpenXR: Unsupported io path " + String(p_ip_path) + String(p_io_path));
if (io_path->openxr_extension_name == "") {
@@ -300,32 +278,31 @@ bool OpenXRAPI::create_instance() {
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
const HashMap<String, bool *> &wrapper_request_extensions = wrapper->get_requested_extensions();
- // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end());
- for (auto &requested_extension : wrapper_request_extensions) {
+ for (const KeyValue<String, bool *> &requested_extension : wrapper_request_extensions) {
requested_extensions[requested_extension.key] = requested_extension.value;
}
}
- // Check which extensions are supported
+ // Check which extensions are supported.
enabled_extensions.clear();
- for (auto &requested_extension : requested_extensions) {
+ for (KeyValue<String, bool *> &requested_extension : requested_extensions) {
if (!is_extension_supported(requested_extension.key)) {
if (requested_extension.value == nullptr) {
- // nullptr means this is a manditory extension so we fail
+ // Null means this is a manditory 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
+ // Set this extension as not supported.
*requested_extension.value = false;
}
} else if (requested_extension.value != nullptr) {
- // set this extension as supported
+ // Set this extension as supported.
*requested_extension.value = true;
- // and record that we want to enable it
+ // And record that we want to enable it.
enabled_extensions.push_back(requested_extension.key.ascii());
} else {
- // record that we want to enable this
+ // Record that we want to enable this.
enabled_extensions.push_back(requested_extension.key.ascii());
}
}
@@ -482,11 +459,19 @@ bool OpenXRAPI::load_supported_view_configuration_types() {
result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations");
+ ERR_FAIL_COND_V_MSG(num_view_configuration_types == 0, false, "OpenXR: Failed to enumerateview configurations"); // JIC there should be at least 1!
for (uint32_t i = 0; i < num_view_configuration_types; i++) {
print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i]));
}
+ // Check value we loaded at startup...
+ if (!is_view_configuration_supported(view_configuration)) {
+ print_verbose(String("OpenXR: ") + OpenXRUtil::get_view_configuration_name(view_configuration) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[0]));
+
+ view_configuration = supported_view_configuration_types[0];
+ }
+
return true;
}
@@ -513,11 +498,19 @@ bool OpenXRAPI::load_supported_environmental_blend_modes() {
result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, num_supported_environment_blend_modes, &num_supported_environment_blend_modes, supported_environment_blend_modes);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate environmental blend modes");
+ ERR_FAIL_COND_V_MSG(num_supported_environment_blend_modes == 0, false, "OpenXR: Failed to enumerate environmental blend modes"); // JIC there should be at least 1!
for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) {
print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i]));
}
+ // Check value we loaded at startup...
+ if (!is_environment_blend_mode_supported(environment_blend_mode)) {
+ print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0]));
+
+ environment_blend_mode = supported_environment_blend_modes[0];
+ }
+
return true;
}
@@ -666,11 +659,19 @@ bool OpenXRAPI::load_supported_reference_spaces() {
result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces");
+ ERR_FAIL_COND_V_MSG(num_reference_spaces == 0, false, "OpenXR: Failed to enumerate reference spaces");
for (uint32_t i = 0; i < num_reference_spaces; i++) {
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;
}
@@ -795,7 +796,7 @@ bool OpenXRAPI::create_swapchains() {
Also Godot only creates a swapchain for the main output.
OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system
- to optimize text rendering and background rendering as OpenXR may choose to re-use the results for reprojection while we're
+ to optimize text rendering and background rendering as OpenXR may choose to reuse the results for reprojection while we're
already rendering the next frame.
Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create,
@@ -1318,6 +1319,10 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) {
ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device.");
}
+ // Also register our rendering extensions
+ register_extension_wrapper(memnew(OpenXRFBUpdateSwapchainExtension(p_rendering_driver)));
+ register_extension_wrapper(memnew(OpenXRFBFoveationExtension(p_rendering_driver)));
+
// initialize
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_before_instance_created();
@@ -1435,7 +1440,9 @@ Size2 OpenXRAPI::get_recommended_target_size() {
XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) {
XrResult result;
- ERR_FAIL_COND_V(!running, XRPose::XR_TRACKING_CONFIDENCE_NONE);
+ if (!running) {
+ return XRPose::XR_TRACKING_CONFIDENCE_NONE;
+ }
// xrWaitFrame not run yet
if (frame_state.predictedDisplayTime == 0) {
@@ -1488,7 +1495,9 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform,
}
bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) {
- ERR_FAIL_COND_V(!running, false);
+ if (!running) {
+ return false;
+ }
// xrWaitFrame not run yet
if (frame_state.predictedDisplayTime == 0) {
@@ -1507,9 +1516,12 @@ bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) {
}
bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) {
- ERR_FAIL_COND_V(!running, false);
ERR_FAIL_NULL_V(graphics_extension, false);
+ if (!running) {
+ return false;
+ }
+
// xrWaitFrame not run yet
if (frame_state.predictedDisplayTime == 0) {
return false;
@@ -1668,7 +1680,7 @@ bool OpenXRAPI::process() {
}
bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) {
- ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // this was not released when it should be, error out and re-use...
+ ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse...
XrResult result;
XrSwapchainImageAcquireInfo swapchain_image_acquire_info = {
@@ -1830,6 +1842,10 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) {
return true;
}
+XrSwapchain OpenXRAPI::get_color_swapchain() {
+ return swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain;
+}
+
RID OpenXRAPI::get_color_texture() {
if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index);
@@ -1918,10 +1934,15 @@ void OpenXRAPI::end_frame() {
}
}
+ XrCompositionLayerFlags layer_flags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
+ if (layers_list.size() > 0 || environment_blend_mode != XR_ENVIRONMENT_BLEND_MODE_OPAQUE) {
+ layer_flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
+ }
+
XrCompositionLayerProjection projection_layer = {
XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type
nullptr, // next
- layers_list.size() > 0 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags
+ layer_flags, // layerFlags
play_space, // space
view_count, // viewCount
projection_views, // views
@@ -1976,6 +1997,55 @@ void OpenXRAPI::set_render_target_size_multiplier(double multiplier) {
render_target_size_multiplier = multiplier;
}
+bool OpenXRAPI::is_foveation_supported() const {
+ OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+ return fov_ext != nullptr && fov_ext->is_enabled();
+}
+
+int OpenXRAPI::get_foveation_level() const {
+ OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+ if (fov_ext != nullptr && fov_ext->is_enabled()) {
+ switch (fov_ext->get_foveation_level()) {
+ case XR_FOVEATION_LEVEL_NONE_FB:
+ return 0;
+ case XR_FOVEATION_LEVEL_LOW_FB:
+ return 1;
+ case XR_FOVEATION_LEVEL_MEDIUM_FB:
+ return 2;
+ case XR_FOVEATION_LEVEL_HIGH_FB:
+ return 3;
+ default:
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+void OpenXRAPI::set_foveation_level(int p_foveation_level) {
+ ERR_FAIL_UNSIGNED_INDEX(p_foveation_level, 4);
+ OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+ if (fov_ext != nullptr && fov_ext->is_enabled()) {
+ XrFoveationLevelFB levels[] = { XR_FOVEATION_LEVEL_NONE_FB, XR_FOVEATION_LEVEL_LOW_FB, XR_FOVEATION_LEVEL_MEDIUM_FB, XR_FOVEATION_LEVEL_HIGH_FB };
+ fov_ext->set_foveation_level(levels[p_foveation_level]);
+ }
+}
+
+bool OpenXRAPI::get_foveation_dynamic() const {
+ OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+ if (fov_ext != nullptr && fov_ext->is_enabled()) {
+ return fov_ext->get_foveation_dynamic() == XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB;
+ }
+ return false;
+}
+
+void OpenXRAPI::set_foveation_dynamic(bool p_foveation_dynamic) {
+ OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+ if (fov_ext != nullptr && fov_ext->is_enabled()) {
+ fov_ext->set_foveation_dynamic(p_foveation_dynamic ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB);
+ }
+}
+
OpenXRAPI::OpenXRAPI() {
// OpenXRAPI is only constructed if OpenXR is enabled.
singleton = this;
@@ -1985,8 +2055,8 @@ OpenXRAPI::OpenXRAPI() {
} else {
// Load settings from project settings
- int ff = GLOBAL_GET("xr/openxr/form_factor");
- switch (ff) {
+ int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor");
+ switch (form_factor_setting) {
case 0: {
form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
} break;
@@ -1997,8 +2067,8 @@ OpenXRAPI::OpenXRAPI() {
break;
}
- int vc = GLOBAL_GET("xr/openxr/view_configuration");
- switch (vc) {
+ int view_configuration_setting = GLOBAL_GET("xr/openxr/view_configuration");
+ switch (view_configuration_setting) {
case 0: {
view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO;
} break;
@@ -2017,8 +2087,8 @@ OpenXRAPI::OpenXRAPI() {
break;
}
- int rs = GLOBAL_GET("xr/openxr/reference_space");
- switch (rs) {
+ int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space");
+ switch (reference_space_setting) {
case 0: {
reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
} break;
@@ -2029,6 +2099,21 @@ OpenXRAPI::OpenXRAPI() {
break;
}
+ int environment_blend_mode_setting = GLOBAL_GET("xr/openxr/environment_blend_mode");
+ switch (environment_blend_mode_setting) {
+ case 0: {
+ environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ } break;
+ case 1: {
+ environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
+ } break;
+ case 2: {
+ environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
+ } break;
+ default:
+ break;
+ }
+
submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer");
}
@@ -2077,7 +2162,7 @@ XRPose::TrackingConfidence _transform_from_location(const T &p_location, Transfo
Basis basis;
Vector3 origin;
XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
- const auto &pose = p_location.pose;
+ const XrPosef &pose = p_location.pose;
// Check orientation
if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
@@ -2274,33 +2359,42 @@ String OpenXRAPI::action_set_get_name(RID p_action_set) {
return action_set->name;
}
-bool OpenXRAPI::action_set_attach(RID p_action_set) {
- ActionSet *action_set = action_set_owner.get_or_null(p_action_set);
- ERR_FAIL_NULL_V(action_set, false);
+bool OpenXRAPI::attach_action_sets(const Vector<RID> &p_action_sets) {
+ ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
- if (action_set->is_attached) {
- // already attached
- return true;
- }
+ Vector<XrActionSet> action_handles;
+ action_handles.resize(p_action_sets.size());
+ for (int i = 0; i < p_action_sets.size(); i++) {
+ ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
+ ERR_FAIL_NULL_V(action_set, false);
- ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
+ if (action_set->is_attached) {
+ return false;
+ }
+
+ action_handles.set(i, action_set->handle);
+ }
// So according to the docs, once we attach our action set to our session it becomes read only..
// https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html
XrSessionActionSetsAttachInfo attach_info = {
XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type
nullptr, // next
- 1, // countActionSets,
- &action_set->handle // actionSets
+ (uint32_t)p_action_sets.size(), // countActionSets,
+ action_handles.ptr() // actionSets
};
XrResult result = xrAttachSessionActionSets(session, &attach_info);
if (XR_FAILED(result)) {
- print_line("OpenXR: failed to attach action set! [", get_error_string(result), "]");
+ print_line("OpenXR: failed to attach action sets! [", get_error_string(result), "]");
return false;
}
- action_set->is_attached = true;
+ for (int i = 0; i < p_action_sets.size(); i++) {
+ ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
+ ERR_FAIL_NULL_V(action_set, false);
+ action_set->is_attached = true;
+ }
/* For debugging:
print_verbose("Attached set " + action_set->name);
@@ -2849,12 +2943,24 @@ const XrEnvironmentBlendMode *OpenXRAPI::get_supported_environment_blend_modes(u
return supported_environment_blend_modes;
}
-bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode mode) {
+bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const {
+ ERR_FAIL_NULL_V(supported_environment_blend_modes, false);
+
for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) {
- if (supported_environment_blend_modes[i] == mode) {
- environment_blend_mode = mode;
+ if (supported_environment_blend_modes[i] == p_blend_mode) {
return true;
}
}
+
+ return false;
+}
+
+bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) {
+ // We allow setting this when not initialized and will check if it is supported when initializing.
+ // After OpenXR is initialized we verify we're setting a supported blend mode.
+ if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) {
+ environment_blend_mode = p_blend_mode;
+ return true;
+ }
return false;
}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 9374cb7afa..89f8f3cbec 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -54,7 +54,6 @@
// Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set.
// forward declarations, we don't want to include these fully
-class OpenXRVulkanExtension;
class OpenXRInterface;
class OpenXRAPI {
@@ -289,7 +288,7 @@ private:
bool on_state_loss_pending();
bool on_state_exiting();
- // convencience
+ // convenience
void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len);
public:
@@ -356,6 +355,7 @@ public:
void pre_render();
bool pre_draw_viewport(RID p_render_target);
+ XrSwapchain get_color_swapchain();
RID get_color_texture();
RID get_depth_texture();
void post_draw_viewport(RID p_render_target);
@@ -370,6 +370,15 @@ public:
double get_render_target_size_multiplier() const;
void set_render_target_size_multiplier(double multiplier);
+ // Foveation settings
+ bool is_foveation_supported() const;
+
+ int get_foveation_level() const;
+ void set_foveation_level(int p_foveation_level);
+
+ bool get_foveation_dynamic() const;
+ void set_foveation_dynamic(bool p_foveation_dynamic);
+
// action map
String get_default_action_map_resource_name();
@@ -380,7 +389,7 @@ public:
RID action_set_create(const String p_name, const String p_localized_name, const int p_priority);
String action_set_get_name(RID p_action_set);
- bool action_set_attach(RID p_action_set);
+ bool attach_action_sets(const Vector<RID> &p_action_sets);
void action_set_free(RID p_action_set);
RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers);
@@ -405,7 +414,9 @@ public:
void unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider);
const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count);
- bool set_environment_blend_mode(XrEnvironmentBlendMode mode);
+ bool is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const;
+ bool set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode);
+ XrEnvironmentBlendMode get_environment_blend_mode() const { return environment_blend_mode; }
OpenXRAPI();
~OpenXRAPI();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 933148da87..7b1530677f 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -34,6 +34,8 @@
#include "core/io/resource_saver.h"
#include "servers/rendering/rendering_server_globals.h"
+#include "extensions/openxr_hand_tracking_extension.h"
+
void OpenXRInterface::_bind_methods() {
// lifecycle signals
ADD_SIGNAL(MethodInfo("session_begun"));
@@ -52,11 +54,71 @@ void OpenXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier", "multiplier"), &OpenXRInterface::set_render_target_size_multiplier);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier"), "set_render_target_size_multiplier", "get_render_target_size_multiplier");
+ // Foveation level
+ ClassDB::bind_method(D_METHOD("is_foveation_supported"), &OpenXRInterface::is_foveation_supported);
+
+ ClassDB::bind_method(D_METHOD("get_foveation_level"), &OpenXRInterface::get_foveation_level);
+ ClassDB::bind_method(D_METHOD("set_foveation_level", "foveation_level"), &OpenXRInterface::set_foveation_level);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "foveation_level"), "set_foveation_level", "get_foveation_level");
+
+ ClassDB::bind_method(D_METHOD("get_foveation_dynamic"), &OpenXRInterface::get_foveation_dynamic);
+ ClassDB::bind_method(D_METHOD("set_foveation_dynamic", "foveation_dynamic"), &OpenXRInterface::set_foveation_dynamic);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "foveation_dynamic"), "set_foveation_dynamic", "get_foveation_dynamic");
+
+ // Action sets
ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active);
ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active);
ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets);
+ // Refresh rates
ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates);
+
+ // Hand tracking.
+ 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_joint_rotation", "hand", "joint"), &OpenXRInterface::get_hand_joint_rotation);
+ ClassDB::bind_method(D_METHOD("get_hand_joint_position", "hand", "joint"), &OpenXRInterface::get_hand_joint_position);
+ ClassDB::bind_method(D_METHOD("get_hand_joint_radius", "hand", "joint"), &OpenXRInterface::get_hand_joint_radius);
+
+ ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_linear_velocity);
+ ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity);
+
+ BIND_ENUM_CONSTANT(HAND_LEFT);
+ BIND_ENUM_CONSTANT(HAND_RIGHT);
+ BIND_ENUM_CONSTANT(HAND_MAX);
+
+ BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_UNOBSTRUCTED);
+ BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER);
+ BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_MAX);
+
+ BIND_ENUM_CONSTANT(HAND_JOINT_PALM);
+ BIND_ENUM_CONSTANT(HAND_JOINT_WRIST);
+ BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_METACARPAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_PROXIMAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_DISTAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_TIP);
+ BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_METACARPAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_PROXIMAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_INTERMEDIATE);
+ BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_DISTAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_TIP);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_METACARPAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_PROXIMAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_INTERMEDIATE);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_DISTAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_TIP);
+ BIND_ENUM_CONSTANT(HAND_JOINT_RING_METACARPAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_RING_PROXIMAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_RING_INTERMEDIATE);
+ BIND_ENUM_CONSTANT(HAND_JOINT_RING_DISTAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_RING_TIP);
+ BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_METACARPAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_PROXIMAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_INTERMEDIATE);
+ BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL);
+ BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP);
+ BIND_ENUM_CONSTANT(HAND_JOINT_MAX);
}
StringName OpenXRInterface::get_name() const {
@@ -555,9 +617,11 @@ bool OpenXRInterface::initialize() {
xr_server->add_tracker(head);
// attach action sets
+ Vector<RID> loaded_action_sets;
for (int i = 0; i < action_sets.size(); i++) {
- openxr_api->action_set_attach(action_sets[i]->action_set_rid);
+ loaded_action_sets.append(action_sets[i]->action_set_rid);
}
+ openxr_api->attach_action_sets(loaded_action_sets);
// make this our primary interface
xr_server->set_primary_interface(this);
@@ -689,6 +753,46 @@ void OpenXRInterface::set_render_target_size_multiplier(double multiplier) {
}
}
+bool OpenXRInterface::is_foveation_supported() const {
+ if (openxr_api == nullptr) {
+ return false;
+ } else {
+ return openxr_api->is_foveation_supported();
+ }
+}
+
+int OpenXRInterface::get_foveation_level() const {
+ if (openxr_api == nullptr) {
+ return 0;
+ } else {
+ return openxr_api->get_foveation_level();
+ }
+}
+
+void OpenXRInterface::set_foveation_level(int p_foveation_level) {
+ if (openxr_api == nullptr) {
+ return;
+ } else {
+ openxr_api->set_foveation_level(p_foveation_level);
+ }
+}
+
+bool OpenXRInterface::get_foveation_dynamic() const {
+ if (openxr_api == nullptr) {
+ return false;
+ } else {
+ return openxr_api->get_foveation_dynamic();
+ }
+}
+
+void OpenXRInterface::set_foveation_dynamic(bool p_foveation_dynamic) {
+ if (openxr_api == nullptr) {
+ return;
+ } else {
+ openxr_api->set_foveation_dynamic(p_foveation_dynamic);
+ }
+}
+
Size2 OpenXRInterface::get_render_target_size() {
if (openxr_api == nullptr) {
return Size2();
@@ -934,6 +1038,27 @@ Array OpenXRInterface::get_supported_environment_blend_modes() {
return modes;
}
+XRInterface::EnvironmentBlendMode OpenXRInterface::get_environment_blend_mode() const {
+ if (openxr_api) {
+ XrEnvironmentBlendMode oxr_blend_mode = openxr_api->get_environment_blend_mode();
+ switch (oxr_blend_mode) {
+ case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: {
+ return XR_ENV_BLEND_MODE_OPAQUE;
+ } break;
+ case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE: {
+ return XR_ENV_BLEND_MODE_ADDITIVE;
+ } break;
+ case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND: {
+ return XR_ENV_BLEND_MODE_ALPHA_BLEND;
+ } break;
+ default:
+ break;
+ }
+ }
+
+ return XR_ENV_BLEND_MODE_OPAQUE;
+}
+
bool OpenXRInterface::set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) {
if (openxr_api) {
XrEnvironmentBlendMode oxr_blend_mode;
@@ -976,6 +1101,96 @@ void OpenXRInterface::on_pose_recentered() {
emit_signal(SNAME("pose_recentered"));
}
+/** Hand tracking. */
+void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange p_motion_range) {
+ ERR_FAIL_INDEX(p_hand, HAND_MAX);
+ ERR_FAIL_INDEX(p_motion_range, HAND_MOTION_RANGE_MAX);
+
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ XrHandJointsMotionRangeEXT xr_motion_range;
+ switch (p_motion_range) {
+ case HAND_MOTION_RANGE_UNOBSTRUCTED:
+ xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
+ break;
+ case HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER:
+ xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT;
+ break;
+ default:
+ // Shouldn't get here, ERR_FAIL_INDEX should have caught this...
+ xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT;
+ break;
+ }
+
+ hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range);
+ }
+}
+
+OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_hand) const {
+ ERR_FAIL_INDEX_V(p_hand, HAND_MAX, HAND_MOTION_RANGE_MAX);
+
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand));
+
+ switch (xr_motion_range) {
+ case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT:
+ return HAND_MOTION_RANGE_UNOBSTRUCTED;
+ case XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT:
+ return HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER;
+ default:
+ ERR_FAIL_V_MSG(HAND_MOTION_RANGE_MAX, "Unknown motion range returned by OpenXR");
+ }
+ }
+
+ return HAND_MOTION_RANGE_MAX;
+}
+
+Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ }
+
+ return Quaternion();
+}
+
+Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ }
+
+ return Vector3();
+}
+
+float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ }
+
+ return 0.0;
+}
+
+Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ }
+
+ return Vector3();
+}
+
+Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ }
+
+ return Vector3();
+}
+
OpenXRInterface::OpenXRInterface() {
openxr_api = OpenXRAPI::get_singleton();
if (openxr_api) {
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 39cc68ae9b..38cf4bdbe8 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -130,6 +130,14 @@ public:
double get_render_target_size_multiplier() const;
void set_render_target_size_multiplier(double multiplier);
+ bool is_foveation_supported() const;
+
+ int get_foveation_level() const;
+ void set_foveation_level(int p_foveation_level);
+
+ bool get_foveation_dynamic() const;
+ void set_foveation_dynamic(bool p_foveation_dynamic);
+
virtual Size2 get_render_target_size() override;
virtual uint32_t get_view_count() override;
virtual Transform3D get_camera_transform() override;
@@ -152,6 +160,7 @@ public:
/** environment blend mode. */
virtual Array get_supported_environment_blend_modes() override;
+ virtual XRInterface::EnvironmentBlendMode get_environment_blend_mode() const override;
virtual bool set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) override;
void on_state_ready();
@@ -161,8 +170,65 @@ public:
void on_pose_recentered();
void tracker_profile_changed(RID p_tracker, RID p_interaction_profile);
+ /** Hand tracking. */
+ enum Hand {
+ HAND_LEFT,
+ HAND_RIGHT,
+ HAND_MAX,
+ };
+
+ enum HandMotionRange {
+ HAND_MOTION_RANGE_UNOBSTRUCTED,
+ HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER,
+ HAND_MOTION_RANGE_MAX
+ };
+
+ void set_motion_range(const Hand p_hand, const HandMotionRange p_motion_range);
+ HandMotionRange get_motion_range(const Hand p_hand) const;
+
+ enum HandJoints {
+ HAND_JOINT_PALM = 0,
+ HAND_JOINT_WRIST = 1,
+ HAND_JOINT_THUMB_METACARPAL = 2,
+ HAND_JOINT_THUMB_PROXIMAL = 3,
+ HAND_JOINT_THUMB_DISTAL = 4,
+ HAND_JOINT_THUMB_TIP = 5,
+ HAND_JOINT_INDEX_METACARPAL = 6,
+ HAND_JOINT_INDEX_PROXIMAL = 7,
+ HAND_JOINT_INDEX_INTERMEDIATE = 8,
+ HAND_JOINT_INDEX_DISTAL = 9,
+ HAND_JOINT_INDEX_TIP = 10,
+ HAND_JOINT_MIDDLE_METACARPAL = 11,
+ HAND_JOINT_MIDDLE_PROXIMAL = 12,
+ HAND_JOINT_MIDDLE_INTERMEDIATE = 13,
+ HAND_JOINT_MIDDLE_DISTAL = 14,
+ HAND_JOINT_MIDDLE_TIP = 15,
+ HAND_JOINT_RING_METACARPAL = 16,
+ HAND_JOINT_RING_PROXIMAL = 17,
+ HAND_JOINT_RING_INTERMEDIATE = 18,
+ HAND_JOINT_RING_DISTAL = 19,
+ HAND_JOINT_RING_TIP = 20,
+ HAND_JOINT_LITTLE_METACARPAL = 21,
+ HAND_JOINT_LITTLE_PROXIMAL = 22,
+ HAND_JOINT_LITTLE_INTERMEDIATE = 23,
+ HAND_JOINT_LITTLE_DISTAL = 24,
+ HAND_JOINT_LITTLE_TIP = 25,
+ HAND_JOINT_MAX = 26,
+ };
+
+ Quaternion get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const;
+ Vector3 get_hand_joint_position(Hand p_hand, HandJoints p_joint) const;
+ float get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const;
+
+ Vector3 get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const;
+ Vector3 get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const;
+
OpenXRInterface();
~OpenXRInterface();
};
+VARIANT_ENUM_CAST(OpenXRInterface::Hand)
+VARIANT_ENUM_CAST(OpenXRInterface::HandMotionRange)
+VARIANT_ENUM_CAST(OpenXRInterface::HandJoints)
+
#endif // OPENXR_INTERFACE_H
diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/openxr/openxr_platform_inc.h
index 87f02cb4c6..6288d1e380 100644
--- a/modules/denoise/denoise_wrapper.cpp
+++ b/modules/openxr/openxr_platform_inc.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* denoise_wrapper.cpp */
+/* openxr_platform_inc.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,39 +28,51 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "denoise_wrapper.h"
+#ifndef OPENXR_PLATFORM_INC_H
+#define OPENXR_PLATFORM_INC_H
-#include <OpenImageDenoise/oidn.h>
+// In various places we need to include platform definitions but we can't
+// include these in our normal header files as we'll end up with issues.
-#include <stdio.h>
+#ifdef VULKAN_ENABLED
+#define XR_USE_GRAPHICS_API_VULKAN
+#include "drivers/vulkan/vulkan_context.h"
+#endif // VULKAN_ENABLED
-void *oidn_denoiser_init() {
- OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU);
- oidnCommitDevice(device);
- return device;
-}
+#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
+#ifdef ANDROID_ENABLED
+#define XR_USE_GRAPHICS_API_OPENGL_ES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+#else
+#define XR_USE_GRAPHICS_API_OPENGL
+#endif // ANDROID_ENABLED
+#ifdef X11_ENABLED
+#define GL_GLEXT_PROTOTYPES 1
+#define GL3_PROTOTYPES 1
+#include "thirdparty/glad/glad/gl.h"
+#include "thirdparty/glad/glad/glx.h"
+#endif // X11_ENABLED
+#endif // defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
-bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) {
- OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr;
- OIDNFilter filter = oidnNewFilter(device, "RTLightmap");
- oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
- oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
- oidnSetFilter1b(filter, "hdr", true);
- //oidnSetFilter1f(filter, "hdrScale", 1.0f);
- oidnCommitFilter(filter);
- oidnExecuteFilter(filter);
+#ifdef X11_ENABLED
+#include <X11/Xlib.h>
+#endif // X11_ENABLED
- const char *msg;
- bool success = true;
- if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) {
- printf("LightmapDenoiser: %s\n", msg);
- success = false;
- }
+#ifdef WINDOWS_ENABLED
+// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform
+// however due to the way the openxr headers are put together, we have no choice.
+#include <windows.h>
+#endif // WINDOWS_ENABLED
- oidnReleaseFilter(filter);
- return success;
-}
+#ifdef ANDROID_ENABLED
+// The jobject type from jni.h is used by openxr_platform.h on Android.
+#include <jni.h>
+#endif // ANDROID_ENABLED
-void oidn_denoiser_finish(void *device) {
- oidnReleaseDevice((OIDNDeviceImpl *)device);
-}
+// Include platform dependent structs.
+#include <openxr/openxr_platform.h>
+
+#endif // OPENXR_PLATFORM_INC_H
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 5d636c2b70..d69c803502 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -34,7 +34,7 @@
#include "action_map/openxr_action_map.h"
#include "action_map/openxr_action_set.h"
#include "action_map/openxr_interaction_profile.h"
-#include "action_map/openxr_interaction_profile_meta_data.h"
+#include "action_map/openxr_interaction_profile_metadata.h"
#include "openxr_interface.h"
#include "extensions/openxr_extension_wrapper_extension.h"
@@ -69,7 +69,7 @@
#endif
static OpenXRAPI *openxr_api = nullptr;
-static OpenXRInteractionProfileMetaData *openxr_interaction_profile_meta_data = nullptr;
+static OpenXRInteractionProfileMetadata *openxr_interaction_profile_metadata = nullptr;
static Ref<OpenXRInterface> openxr_interface;
#ifdef TOOLS_ENABLED
@@ -77,10 +77,10 @@ static void _editor_init() {
if (OpenXRAPI::openxr_is_enabled(false)) {
// Only add our OpenXR action map editor if OpenXR is enabled for our project
- if (openxr_interaction_profile_meta_data == nullptr) {
- // If we didn't initialize our actionmap meta data at startup, we initialize it now.
- openxr_interaction_profile_meta_data = memnew(OpenXRInteractionProfileMetaData);
- ERR_FAIL_NULL(openxr_interaction_profile_meta_data);
+ if (openxr_interaction_profile_metadata == nullptr) {
+ // If we didn't initialize our actionmap metadata at startup, we initialize it now.
+ openxr_interaction_profile_metadata = memnew(OpenXRInteractionProfileMetadata);
+ ERR_FAIL_NULL(openxr_interaction_profile_metadata);
}
OpenXREditorPlugin *openxr_plugin = memnew(OpenXREditorPlugin());
@@ -118,8 +118,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
}
if (OpenXRAPI::openxr_is_enabled()) {
- openxr_interaction_profile_meta_data = memnew(OpenXRInteractionProfileMetaData);
- ERR_FAIL_NULL(openxr_interaction_profile_meta_data);
+ openxr_interaction_profile_metadata = memnew(OpenXRInteractionProfileMetadata);
+ ERR_FAIL_NULL(openxr_interaction_profile_metadata);
openxr_api = memnew(OpenXRAPI);
ERR_FAIL_NULL(openxr_api);
@@ -150,7 +150,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(OpenXRAction);
GDREGISTER_CLASS(OpenXRActionSet);
GDREGISTER_CLASS(OpenXRActionMap);
- GDREGISTER_CLASS(OpenXRInteractionProfileMetaData);
+ GDREGISTER_CLASS(OpenXRInteractionProfileMetadata);
GDREGISTER_CLASS(OpenXRIPBinding);
GDREGISTER_CLASS(OpenXRInteractionProfile);
@@ -203,9 +203,9 @@ void uninitialize_openxr_module(ModuleInitializationLevel p_level) {
openxr_api = nullptr;
}
- if (openxr_interaction_profile_meta_data) {
- memdelete(openxr_interaction_profile_meta_data);
- openxr_interaction_profile_meta_data = nullptr;
+ if (openxr_interaction_profile_metadata) {
+ memdelete(openxr_interaction_profile_metadata);
+ openxr_interaction_profile_metadata = nullptr;
}
// cleanup our extension wrappers
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index 91571a556d..bedc8874d6 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -213,8 +213,8 @@ void OpenXRHand::_update_skeleton() {
quaternions[i] = Quaternion();
positions[i] = Vector3();
- const auto &location = hand_tracker->joint_locations[i];
- const auto &pose = location.pose;
+ const XrHandJointLocationEXT &location = hand_tracker->joint_locations[i];
+ const XrPosef &pose = location.pose;
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) {
diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h
index 7bec3959d8..edfb474ac7 100644
--- a/modules/openxr/scene/openxr_hand.h
+++ b/modules/openxr/scene/openxr_hand.h
@@ -43,13 +43,13 @@ class OpenXRHand : public Node3D {
GDCLASS(OpenXRHand, Node3D);
public:
- enum Hands {
+ enum Hands { // Deprecated, need to change this to OpenXRInterface::Hands.
HAND_LEFT,
HAND_RIGHT,
HAND_MAX
};
- enum MotionRange {
+ enum MotionRange { // Deprecated, need to change this to OpenXRInterface::HandMotionRange.
MOTION_RANGE_UNOBSTRUCTED,
MOTION_RANGE_CONFORM_TO_CONTROLLER,
MOTION_RANGE_MAX
diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp
index 69fbf87483..5005000eae 100644
--- a/modules/raycast/raycast_occlusion_cull.cpp
+++ b/modules/raycast/raycast_occlusion_cull.cpp
@@ -221,7 +221,7 @@ void RaycastOcclusionCull::occluder_initialize(RID p_occluder) {
void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) {
Occluder *occluder = occluder_owner.get_or_null(p_occluder);
- ERR_FAIL_COND(!occluder);
+ ERR_FAIL_NULL(occluder);
occluder->vertices = p_vertices;
occluder->indices = p_indices;
@@ -242,7 +242,7 @@ void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3
void RaycastOcclusionCull::free_occluder(RID p_occluder) {
Occluder *occluder = occluder_owner.get_or_null(p_occluder);
- ERR_FAIL_COND(!occluder);
+ ERR_FAIL_NULL(occluder);
memdelete(occluder);
occluder_owner.free(p_occluder);
}
@@ -250,17 +250,15 @@ void RaycastOcclusionCull::free_occluder(RID p_occluder) {
////////////////////////////////////////////////////////
void RaycastOcclusionCull::add_scenario(RID p_scenario) {
- if (scenarios.has(p_scenario)) {
- scenarios[p_scenario].removed = false;
- } else {
- scenarios[p_scenario] = Scenario();
- }
+ ERR_FAIL_COND(scenarios.has(p_scenario));
+ scenarios[p_scenario] = Scenario();
}
void RaycastOcclusionCull::remove_scenario(RID p_scenario) {
- ERR_FAIL_COND(!scenarios.has(p_scenario));
- Scenario &scenario = scenarios[p_scenario];
- scenario.removed = true;
+ Scenario *scenario = scenarios.getptr(p_scenario);
+ ERR_FAIL_NULL(scenario);
+ scenario->free();
+ scenarios.erase(p_scenario);
}
void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform3D &p_xform, bool p_enabled) {
@@ -291,7 +289,7 @@ void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance,
if (p_occluder.is_valid()) {
Occluder *occluder = occluder_owner.get_or_null(p_occluder);
- ERR_FAIL_COND(!occluder);
+ ERR_FAIL_NULL(occluder);
occluder->users.insert(InstanceID(p_scenario, p_instance));
}
changed = true;
@@ -390,6 +388,23 @@ void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_
}
}
+void RaycastOcclusionCull::Scenario::free() {
+ if (commit_thread) {
+ if (commit_thread->is_started()) {
+ commit_thread->wait_to_finish();
+ }
+ memdelete(commit_thread);
+ commit_thread = nullptr;
+ }
+
+ for (int i = 0; i < 2; i++) {
+ if (ebr_scene[i]) {
+ rtcReleaseScene(ebr_scene[i]);
+ ebr_scene[i] = nullptr;
+ }
+ }
+}
+
void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) {
Scenario *scenario = (Scenario *)p_ud;
int commit_idx = 1 - (scenario->current_scene_idx);
@@ -397,8 +412,8 @@ void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) {
scenario->commit_done = true;
}
-bool RaycastOcclusionCull::Scenario::update() {
- ERR_FAIL_COND_V(singleton == nullptr, false);
+void RaycastOcclusionCull::Scenario::update() {
+ ERR_FAIL_NULL(singleton);
if (commit_thread == nullptr) {
commit_thread = memnew(Thread);
@@ -409,22 +424,12 @@ bool RaycastOcclusionCull::Scenario::update() {
commit_thread->wait_to_finish();
current_scene_idx = 1 - current_scene_idx;
} else {
- return false;
- }
- }
-
- if (removed) {
- if (ebr_scene[0]) {
- rtcReleaseScene(ebr_scene[0]);
+ return;
}
- if (ebr_scene[1]) {
- rtcReleaseScene(ebr_scene[1]);
- }
- return true;
}
if (!dirty && removed_instances.is_empty() && dirty_instances_array.is_empty()) {
- return false;
+ return;
}
for (const RID &scenario : removed_instances) {
@@ -480,7 +485,6 @@ bool RaycastOcclusionCull::Scenario::update() {
dirty = false;
commit_done = false;
commit_thread->start(&Scenario::_commit_scene, this);
- return false;
}
void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThreadData *p_raycast_data) const {
@@ -492,7 +496,7 @@ void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThrea
}
void RaycastOcclusionCull::Scenario::raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const {
- ERR_FAIL_COND(singleton == nullptr);
+ ERR_FAIL_NULL(singleton);
if (raycast_singleton->ebr_device == nullptr) {
return; // Embree is initialized on demand when there is some scenario with occluders in it.
}
@@ -544,13 +548,7 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_
}
Scenario &scenario = scenarios[buffer.scenario_rid];
-
- bool removed = scenario.update();
-
- if (removed) {
- scenarios.erase(buffer.scenario_rid);
- return;
- }
+ scenario.update();
buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal);
@@ -603,19 +601,7 @@ RaycastOcclusionCull::RaycastOcclusionCull() {
RaycastOcclusionCull::~RaycastOcclusionCull() {
for (KeyValue<RID, Scenario> &K : scenarios) {
- Scenario &scenario = K.value;
- if (scenario.commit_thread) {
- if (scenario.commit_thread->is_started()) {
- scenario.commit_thread->wait_to_finish();
- }
- memdelete(scenario.commit_thread);
- }
-
- for (int i = 0; i < 2; i++) {
- if (scenario.ebr_scene[i]) {
- rtcReleaseScene(scenario.ebr_scene[i]);
- }
- }
+ K.value.free();
}
if (ebr_device != nullptr) {
diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h
index c4e733b664..ab5eb4eaf0 100644
--- a/modules/raycast/raycast_occlusion_cull.h
+++ b/modules/raycast/raycast_occlusion_cull.h
@@ -132,7 +132,6 @@ private:
Thread *commit_thread = nullptr;
bool commit_done = true;
bool dirty = false;
- bool removed = false;
RTCScene ebr_scene[2] = { nullptr, nullptr };
int current_scene_idx = 0;
@@ -147,7 +146,8 @@ private:
void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data);
void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to);
static void _commit_scene(void *p_ud);
- bool update();
+ void free();
+ void update();
void _raycast(uint32_t p_thread, const RaycastThreadData *p_raycast_data) const;
void raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const;
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 5770e7155e..ab74fce3a9 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -10,7 +10,7 @@
var regex = RegEx.new()
regex.compile("\\w-(\\d+)")
[/codeblock]
- The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code].
+ The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. In GDScript, you can also use raw string literals (r-strings). For example, [code]compile(r'"(?:\\.|[^"])*"')[/code] would be read the same.
Using [method search], you can find the pattern within the given text. If a pattern is found, [RegExMatch] is returned and you can retrieve details of the results using methods such as [method RegExMatch.get_string] and [method RegExMatch.get_start].
[codeblock]
var regex = RegEx.new()
diff --git a/modules/regex/icons/RegEx.svg b/modules/regex/icons/RegEx.svg
new file mode 100644
index 0000000000..4df26f41c0
--- /dev/null
+++ b/modules/regex/icons/RegEx.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 14h3v-3H2zM6.561 2.855a21 21 0 0 1 2.82 1.185A21 21 0 0 1 9.137 1h1.77a21 21 0 0 1-.28 3.027 21 21 0 0 1 2.88-1.171l.562 1.733a21 21 0 0 1-3.04.684 21 21 0 0 1 2.1 2.307l-1.465 1.037a21 21 0 0 1-1.672-2.624 21 21 0 0 1-1.587 2.624L6.965 7.58a21 21 0 0 1 2.026-2.308A21 21 0 0 1 6 4.59z" fill="#e0e0e0"/></svg>
diff --git a/modules/regex/icons/RegExMatch.svg b/modules/regex/icons/RegExMatch.svg
new file mode 100644
index 0000000000..889cf6cc8a
--- /dev/null
+++ b/modules/regex/icons/RegExMatch.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M5 13h2v-2H5zm2.5-8a14 14 0 0 1 1.88.79 14 14 0 0 1-.163-2.027h1.18a14 14 0 0 1-.186 2.018 14 14 0 0 1 1.92-.78l.374 1.155a14 14 0 0 1-2.026.456 14 14 0 0 1 1.4 1.538l-.977.691a14 14 0 0 1-1.115-1.75 14 14 0 0 1-1.058 1.75l-.96-.691A14 14 0 0 1 9.12 6.61a14 14 0 0 1-1.993-.454zM1.67 2C0 5 0 11 1.67 14h2C2 11 2 5 3.67 2zm10.66 0c1.67 3 1.67 9 0 12h2c1.67-3 1.67-9 0-12z" fill="#e0e0e0"/></svg>
diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h
index 6515d5d130..3e4d769377 100644
--- a/modules/regex/tests/test_regex.h
+++ b/modules/regex/tests/test_regex.h
@@ -83,9 +83,16 @@ TEST_CASE("[RegEx] Searching") {
REQUIRE(match != nullptr);
CHECK(match->get_string(0) == "ea");
+ match = re.search(s, 1, 2);
+ REQUIRE(match != nullptr);
+ CHECK(match->get_string(0) == "e");
match = re.search(s, 2, 4);
REQUIRE(match != nullptr);
CHECK(match->get_string(0) == "a");
+ match = re.search(s, 3, 5);
+ CHECK(match == nullptr);
+ match = re.search(s, 6, 2);
+ CHECK(match == nullptr);
const Array all_results = re.search_all(s);
CHECK(all_results.size() == 2);
@@ -103,11 +110,45 @@ TEST_CASE("[RegEx] Searching") {
}
TEST_CASE("[RegEx] Substitution") {
- String s = "Double all the vowels.";
+ const String s1 = "Double all the vowels.";
- RegEx re("(?<vowel>[aeiou])");
- REQUIRE(re.is_valid());
- CHECK(re.sub(s, "$0$vowel", true) == "Doouublee aall thee vooweels.");
+ RegEx re1("(?<vowel>[aeiou])");
+ REQUIRE(re1.is_valid());
+ CHECK(re1.sub(s1, "$0$vowel", true) == "Doouublee aall thee vooweels.");
+
+ const String s2 = "Substitution with group.";
+
+ RegEx re2("Substitution (.+)");
+ REQUIRE(re2.is_valid());
+ CHECK(re2.sub(s2, "Test ${1}") == "Test with group.");
+
+ const String s3 = "Useless substitution";
+
+ RegEx re3("Anything");
+ REQUIRE(re3.is_valid());
+ CHECK(re3.sub(s3, "Something") == "Useless substitution");
+
+ const String s4 = "acacac";
+
+ RegEx re4("(a)(b){0}(c)");
+ REQUIRE(re4.is_valid());
+ CHECK(re4.sub(s4, "${1}.${3}.", true) == "a.c.a.c.a.c.");
+}
+
+TEST_CASE("[RegEx] Substitution with empty input and/or replacement") {
+ const String s1 = "";
+ const String s2 = "gogogo";
+
+ RegEx re1("");
+ REQUIRE(re1.is_valid());
+ CHECK(re1.sub(s1, "") == "");
+ CHECK(re1.sub(s1, "a") == "a");
+ CHECK(re1.sub(s2, "") == "gogogo");
+
+ RegEx re2("go");
+ REQUIRE(re2.is_valid());
+ CHECK(re2.sub(s2, "") == "gogo");
+ CHECK(re2.sub(s2, "", true) == "");
}
TEST_CASE("[RegEx] Uninitialized use") {
@@ -150,6 +191,37 @@ TEST_CASE("[RegEx] Invalid end position") {
CHECK(re.sub(s, "", true, 0, 10) == "Gdt");
}
+
+TEST_CASE("[RegEx] Get match string list") {
+ const String s = "Godot Engine";
+
+ RegEx re("(Go)(dot)");
+ Ref<RegExMatch> match = re.search(s);
+ REQUIRE(match != nullptr);
+ PackedStringArray result;
+ result.append("Godot");
+ result.append("Go");
+ result.append("dot");
+ CHECK(match->get_strings() == result);
+}
+
+TEST_CASE("[RegEx] Match start and end positions") {
+ const String s = "Whole pattern";
+
+ RegEx re1("pattern");
+ REQUIRE(re1.is_valid());
+ Ref<RegExMatch> match = re1.search(s);
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start(0) == 6);
+ CHECK(match->get_end(0) == 13);
+
+ RegEx re2("(?<vowel>[aeiou])");
+ REQUIRE(re2.is_valid());
+ match = re2.search(s);
+ REQUIRE(match != nullptr);
+ CHECK(match->get_start("vowel") == 2);
+ CHECK(match->get_end("vowel") == 3);
+}
} // namespace TestRegEx
#endif // TEST_REGEX_H
diff --git a/modules/svg/SCsub b/modules/svg/SCsub
index ae3e1bdedb..a99bc8df60 100644
--- a/modules/svg/SCsub
+++ b/modules/svg/SCsub
@@ -11,69 +11,65 @@ thirdparty_obj = []
thirdparty_dir = "#thirdparty/thorvg/"
thirdparty_sources = [
- "src/lib/sw_engine/tvgSwFill.cpp",
- "src/lib/sw_engine/tvgSwImage.cpp",
- "src/lib/sw_engine/tvgSwMath.cpp",
- "src/lib/sw_engine/tvgSwMemPool.cpp",
- "src/lib/sw_engine/tvgSwRaster.cpp",
- "src/lib/sw_engine/tvgSwRenderer.cpp",
- "src/lib/sw_engine/tvgSwRle.cpp",
- "src/lib/sw_engine/tvgSwShape.cpp",
- "src/lib/sw_engine/tvgSwStroke.cpp",
- "src/lib/tvgAccessor.cpp",
- "src/lib/tvgBezier.cpp",
- "src/lib/tvgCanvas.cpp",
- "src/lib/tvgFill.cpp",
- "src/lib/tvgGlCanvas.cpp",
- "src/lib/tvgInitializer.cpp",
- "src/lib/tvgLinearGradient.cpp",
- "src/lib/tvgLoader.cpp",
- "src/lib/tvgLzw.cpp",
- "src/lib/tvgPaint.cpp",
- "src/lib/tvgPicture.cpp",
- "src/lib/tvgRadialGradient.cpp",
- "src/lib/tvgRender.cpp",
- "src/lib/tvgSaver.cpp",
- "src/lib/tvgScene.cpp",
- "src/lib/tvgShape.cpp",
- "src/lib/tvgSwCanvas.cpp",
- "src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
- "src/loaders/raw/tvgRawLoader.cpp",
+ # common
+ "src/common/tvgBezier.cpp",
+ "src/common/tvgCompressor.cpp",
+ "src/common/tvgMath.cpp",
+ "src/common/tvgStr.cpp",
+ # SVG parser
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
"src/loaders/svg/tvgSvgPath.cpp",
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
+ "src/loaders/raw/tvgRawLoader.cpp",
+ # renderer common
+ "src/renderer/tvgAccessor.cpp",
+ # "src/renderer/tvgAnimation.cpp",
+ "src/renderer/tvgCanvas.cpp",
+ "src/renderer/tvgFill.cpp",
+ # "src/renderer/tvgGlCanvas.cpp",
+ "src/renderer/tvgInitializer.cpp",
+ "src/renderer/tvgLoader.cpp",
+ "src/renderer/tvgPaint.cpp",
+ "src/renderer/tvgPicture.cpp",
+ "src/renderer/tvgRender.cpp",
+ # "src/renderer/tvgSaver.cpp",
+ "src/renderer/tvgScene.cpp",
+ "src/renderer/tvgShape.cpp",
+ "src/renderer/tvgSwCanvas.cpp",
+ "src/renderer/tvgTaskScheduler.cpp",
+ # renderer sw_engine
+ "src/renderer/sw_engine/tvgSwFill.cpp",
+ "src/renderer/sw_engine/tvgSwImage.cpp",
+ "src/renderer/sw_engine/tvgSwMath.cpp",
+ "src/renderer/sw_engine/tvgSwMemPool.cpp",
+ "src/renderer/sw_engine/tvgSwRaster.cpp",
+ "src/renderer/sw_engine/tvgSwRenderer.cpp",
+ "src/renderer/sw_engine/tvgSwRle.cpp",
+ "src/renderer/sw_engine/tvgSwShape.cpp",
+ "src/renderer/sw_engine/tvgSwStroke.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"])
+# Enable ThorVG static object linking.
+env_svg.Append(CPPDEFINES=["TVG_STATIC"])
+
env_thirdparty = env_svg.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.Prepend(
CPPPATH=[
- thirdparty_dir + "src/lib",
- thirdparty_dir + "src/lib/sw_engine",
- thirdparty_dir + "src/loaders/external_png",
- thirdparty_dir + "src/loaders/jpg",
- thirdparty_dir + "src/loaders/raw",
+ thirdparty_dir + "src/common",
thirdparty_dir + "src/loaders/svg",
- thirdparty_dir + "src/loaders/tvg",
- thirdparty_dir + "src/savers/tvg",
+ thirdparty_dir + "src/renderer",
+ thirdparty_dir + "src/renderer/sw_engine",
+ thirdparty_dir + "src/loaders/raw",
]
)
-# Also requires libpng headers
-if env["builtin_libpng"]:
- env_thirdparty.Prepend(CPPPATH=["#thirdparty/libpng"])
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index 7639155914..a542bbc234 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -107,7 +107,7 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
// Note: memalloc here, be sure to memfree before any return.
uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
- tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index bf828cf77a..3c468e61d7 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -39,7 +39,15 @@ freetype_enabled = "freetype" in env.module_list
msdfgen_enabled = "msdfgen" in env.module_list
if "svg" in env.module_list:
- env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+ env_text_server_adv.Prepend(
+ CPPPATH=[
+ "#thirdparty/thorvg/inc",
+ "#thirdparty/thorvg/src/common",
+ "#thirdparty/thorvg/src/renderer",
+ ]
+ )
+ # Enable ThorVG static object linking.
+ env_text_server_adv.Append(CPPDEFINES=["TVG_STATIC"])
if env["builtin_harfbuzz"]:
env_harfbuzz = env_modules.Clone()
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index af9dae84e3..b95c35f80d 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -42,64 +42,69 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
thirdparty_tvg_sources = [
- "src/lib/sw_engine/tvgSwFill.cpp",
- "src/lib/sw_engine/tvgSwImage.cpp",
- "src/lib/sw_engine/tvgSwMath.cpp",
- "src/lib/sw_engine/tvgSwMemPool.cpp",
- "src/lib/sw_engine/tvgSwRaster.cpp",
- "src/lib/sw_engine/tvgSwRenderer.cpp",
- "src/lib/sw_engine/tvgSwRle.cpp",
- "src/lib/sw_engine/tvgSwShape.cpp",
- "src/lib/sw_engine/tvgSwStroke.cpp",
- "src/lib/tvgAccessor.cpp",
- "src/lib/tvgBezier.cpp",
- "src/lib/tvgCanvas.cpp",
- "src/lib/tvgFill.cpp",
- "src/lib/tvgGlCanvas.cpp",
- "src/lib/tvgInitializer.cpp",
- "src/lib/tvgLinearGradient.cpp",
- "src/lib/tvgLoader.cpp",
- "src/lib/tvgLzw.cpp",
- "src/lib/tvgPaint.cpp",
- "src/lib/tvgPicture.cpp",
- "src/lib/tvgRadialGradient.cpp",
- "src/lib/tvgRender.cpp",
- "src/lib/tvgSaver.cpp",
- "src/lib/tvgScene.cpp",
- "src/lib/tvgShape.cpp",
- "src/lib/tvgSwCanvas.cpp",
- "src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
- "src/loaders/raw/tvgRawLoader.cpp",
+ # common
+ "src/common/tvgBezier.cpp",
+ "src/common/tvgCompressor.cpp",
+ "src/common/tvgMath.cpp",
+ "src/common/tvgStr.cpp",
+ # SVG parser
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
"src/loaders/svg/tvgSvgPath.cpp",
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
+ "src/loaders/raw/tvgRawLoader.cpp",
+ # renderer common
+ "src/renderer/tvgAccessor.cpp",
+ # "src/renderer/tvgAnimation.cpp",
+ "src/renderer/tvgCanvas.cpp",
+ "src/renderer/tvgFill.cpp",
+ # "src/renderer/tvgGlCanvas.cpp",
+ "src/renderer/tvgInitializer.cpp",
+ "src/renderer/tvgLoader.cpp",
+ "src/renderer/tvgPaint.cpp",
+ "src/renderer/tvgPicture.cpp",
+ "src/renderer/tvgRender.cpp",
+ # "src/renderer/tvgSaver.cpp",
+ "src/renderer/tvgScene.cpp",
+ "src/renderer/tvgShape.cpp",
+ "src/renderer/tvgSwCanvas.cpp",
+ "src/renderer/tvgTaskScheduler.cpp",
+ # renderer sw_engine
+ "src/renderer/sw_engine/tvgSwFill.cpp",
+ "src/renderer/sw_engine/tvgSwImage.cpp",
+ "src/renderer/sw_engine/tvgSwMath.cpp",
+ "src/renderer/sw_engine/tvgSwMemPool.cpp",
+ "src/renderer/sw_engine/tvgSwRaster.cpp",
+ "src/renderer/sw_engine/tvgSwRenderer.cpp",
+ "src/renderer/sw_engine/tvgSwRle.cpp",
+ "src/renderer/sw_engine/tvgSwShape.cpp",
+ "src/renderer/sw_engine/tvgSwStroke.cpp",
]
thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
env_tvg.Append(
CPPPATH=[
"../../../thirdparty/thorvg/inc",
- "../../../thirdparty/thorvg/src/lib",
- "../../../thirdparty/thorvg/src/lib/sw_engine",
- "../../../thirdparty/thorvg/src/loaders/external_png",
- "../../../thirdparty/thorvg/src/loaders/jpg",
- "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/common",
"../../../thirdparty/thorvg/src/loaders/svg",
- "../../../thirdparty/thorvg/src/loaders/tvg",
- "../../../thirdparty/thorvg/src/savers/tvg",
- "../../../thirdparty/libpng",
+ "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/renderer",
+ "../../../thirdparty/thorvg/src/renderer/sw_engine",
+ ]
+ )
+
+ # Enable ThorVG static object linking.
+ env_tvg.Append(CPPDEFINES=["TVG_STATIC"])
+
+ env.Append(
+ CPPPATH=[
+ "../../../thirdparty/thorvg/inc",
+ "../../../thirdparty/thorvg/src/common",
+ "../../../thirdparty/thorvg/src/renderer",
]
)
- env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
lib = env_tvg.Library(
@@ -262,7 +267,6 @@ if env["freetype_enabled"]:
CPPDEFINES=[
"FT2_BUILD_LIBRARY",
"FT_CONFIG_OPTION_USE_PNG",
- ("PNG_ARM_NEON_OPT", 0),
"FT_CONFIG_OPTION_SYSTEM_ZLIB",
]
)
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 13d8a2c17a..9adb10236e 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -1810,7 +1810,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d
hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, nullptr);
+ ERR_FAIL_NULL_V(fd, nullptr);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1830,7 +1830,7 @@ RID TextServerAdvanced::_create_font() {
void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
@@ -1841,7 +1841,7 @@ void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteA
void TextServerAdvanced::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
@@ -1855,7 +1855,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index >= 0x7FFF);
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->face_index != p_face_index) {
@@ -1866,7 +1866,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
return fd->face_index;
@@ -1874,7 +1874,7 @@ int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
int face_count = 0;
@@ -1920,7 +1920,7 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1930,7 +1930,7 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1940,7 +1940,7 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1950,7 +1950,7 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1960,7 +1960,7 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1970,7 +1970,7 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 400);
+ ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1980,7 +1980,7 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1990,7 +1990,7 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 100);
+ ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2000,7 +2000,7 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2010,7 +2010,7 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2020,7 +2020,7 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2113,12 +2113,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
name = vformat("unknown_%d", names[i].name_id);
} break;
}
+ String text;
unsigned int text_size = hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, nullptr, nullptr) + 1;
- // @todo After godot-cpp#1141 is fixed, use text.resize() and write directly to text.wptr() instead of using a temporary buffer.
- char32_t *buffer = memnew_arr(char32_t, text_size);
- hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)buffer);
- String text(buffer);
- memdelete_arr(buffer);
+ text.resize(text_size);
+ hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)text.ptrw());
if (!text.is_empty()) {
Dictionary &id_string = names_for_lang[String(hb_language_to_string(names[i].language))];
id_string[name] = text;
@@ -2135,7 +2133,7 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->antialiasing != p_antialiasing) {
@@ -2146,7 +2144,7 @@ void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServe
TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE);
+ ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
return fd->antialiasing;
@@ -2154,7 +2152,7 @@ TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RI
void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->mipmaps != p_generate_mipmaps) {
@@ -2170,7 +2168,7 @@ void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool
bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->mipmaps;
@@ -2178,7 +2176,7 @@ bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const
void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf != p_msdf) {
@@ -2189,7 +2187,7 @@ void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID
bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf;
@@ -2197,7 +2195,7 @@ bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &
void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf_range != p_msdf_pixel_range) {
@@ -2208,7 +2206,7 @@ void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_range;
@@ -2216,7 +2214,7 @@ int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) co
void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf_source_size != p_msdf_size) {
@@ -2227,7 +2225,7 @@ void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_source_size;
@@ -2235,7 +2233,7 @@ int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->fixed_size = p_fixed_size;
@@ -2243,7 +2241,7 @@ void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->fixed_size;
@@ -2251,7 +2249,7 @@ int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->allow_system_fallback = p_allow_system_fallback;
@@ -2259,7 +2257,7 @@ void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid,
bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->allow_system_fallback;
@@ -2267,7 +2265,7 @@ bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) c
void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->force_autohinter != p_force_autohinter) {
@@ -2278,7 +2276,7 @@ void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool
bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->force_autohinter;
@@ -2286,7 +2284,7 @@ bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const
void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->hinting != p_hinting) {
@@ -2297,7 +2295,7 @@ void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, HINTING_NONE);
+ ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
return fd->hinting;
@@ -2305,7 +2303,7 @@ TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid)
void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->subpixel_positioning = p_subpixel;
@@ -2313,7 +2311,7 @@ void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, T
TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioning(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED);
+ ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
return fd->subpixel_positioning;
@@ -2321,7 +2319,7 @@ TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioni
void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->embolden != p_strength) {
@@ -2332,15 +2330,38 @@ void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_stre
double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
return fd->embolden;
}
+void TextServerAdvanced::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
+ ERR_FAIL_INDEX((int)p_spacing, 4);
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ _font_clear_cache(fd);
+ fd->extra_spacing[p_spacing] = p_value;
+ }
+}
+
+int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
+ ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
+
+ MutexLock lock(fd->mutex);
+
+ return fd->extra_spacing[p_spacing];
+}
+
void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->transform != p_transform) {
@@ -2351,7 +2372,7 @@ void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transf
Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Transform2D());
+ ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
return fd->transform;
@@ -2359,18 +2380,18 @@ Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const
void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
- if (fd->variation_coordinates != p_variation_coordinates) {
+ if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) {
_font_clear_cache(fd);
- fd->variation_coordinates = p_variation_coordinates;
+ fd->variation_coordinates = p_variation_coordinates.duplicate();
}
}
Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->variation_coordinates;
@@ -2378,7 +2399,7 @@ Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font
void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->oversampling != p_oversampling) {
@@ -2389,7 +2410,7 @@ void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_
double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
return fd->oversampling;
@@ -2397,7 +2418,7 @@ double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
+ ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
TypedArray<Vector2i> ret;
@@ -2409,7 +2430,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_
void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
@@ -2421,7 +2442,7 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
@@ -2433,7 +2454,7 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve
void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2444,7 +2465,7 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2460,7 +2481,7 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -2470,7 +2491,7 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2486,7 +2507,7 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2497,7 +2518,7 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2513,7 +2534,7 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2524,7 +2545,7 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2540,7 +2561,7 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2556,7 +2577,7 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2572,7 +2593,7 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2584,7 +2605,7 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2594,7 +2615,7 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2606,7 +2627,7 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
MutexLock lock(fd->mutex);
@@ -2635,7 +2656,7 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Ref<Image>());
+ ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2649,7 +2670,7 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2668,7 +2689,7 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedInt32Array());
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2693,7 +2714,7 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedInt32Array());
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2709,7 +2730,7 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2720,7 +2741,7 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2731,7 +2752,7 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
const FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_font_size);
@@ -2745,7 +2766,7 @@ double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) c
Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2783,7 +2804,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2798,7 +2819,7 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2828,7 +2849,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2843,7 +2864,7 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2873,7 +2894,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2888,7 +2909,7 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Rect2());
+ ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2913,7 +2934,7 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2928,7 +2949,7 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, -1);
+ ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2953,7 +2974,7 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2968,7 +2989,7 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, RID());
+ ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -3014,7 +3035,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Size2());
+ ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -3060,7 +3081,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3110,7 +3131,7 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
+ ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3126,7 +3147,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3137,7 +3158,7 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3148,7 +3169,7 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3159,7 +3180,7 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3192,7 +3213,7 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
ERR_FAIL_COND_V_MSG((p_variation_selector >= 0xd800 && p_variation_selector <= 0xdfff) || (p_variation_selector > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_variation_selector, 16) + ".");
@@ -3217,7 +3238,7 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3269,7 +3290,7 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
if (fd->cache.is_empty()) {
@@ -3302,7 +3323,7 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -3337,7 +3358,7 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -3368,7 +3389,7 @@ 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 {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -3460,7 +3481,7 @@ 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 {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
@@ -3552,7 +3573,7 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
if (fd->language_support_overrides.has(p_language)) {
@@ -3564,7 +3585,7 @@ bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, cons
void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides[p_language] = p_supported;
@@ -3572,7 +3593,7 @@ void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_r
bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->language_support_overrides[p_language];
@@ -3580,7 +3601,7 @@ bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_r
void TextServerAdvanced::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides.erase(p_language);
@@ -3588,7 +3609,7 @@ void TextServerAdvanced::_font_remove_language_support_override(const RID &p_fon
PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const RID &p_font_rid) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedStringArray());
+ ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
@@ -3600,7 +3621,7 @@ PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const
bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
if (fd->script_support_overrides.has(p_script)) {
@@ -3614,7 +3635,7 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->script_support_overrides[p_script] = p_supported;
@@ -3622,7 +3643,7 @@ void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid
bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->script_support_overrides[p_script];
@@ -3630,7 +3651,7 @@ bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid
void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->script_support_overrides.erase(p_script);
@@ -3638,7 +3659,7 @@ void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_
PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const RID &p_font_rid) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedStringArray());
+ ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
@@ -3650,7 +3671,7 @@ PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const R
void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -3660,7 +3681,7 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->feature_overrides;
@@ -3668,7 +3689,7 @@ Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p
Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -3678,7 +3699,7 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_rid) const {
FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -3782,6 +3803,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
p_shaped->script_iter = nullptr;
}
p_shaped->break_ops_valid = false;
+ p_shaped->chars_valid = false;
p_shaped->js_ops_valid = false;
}
}
@@ -3821,7 +3843,7 @@ RID TextServerAdvanced::_create_shaped_text(TextServer::Direction p_direction, T
void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
sd->parent = RID();
@@ -3837,7 +3859,7 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) {
void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->direction != p_direction) {
@@ -3851,7 +3873,7 @@ void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextSer
TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR);
+ ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR);
MutexLock lock(sd->mutex);
return sd->direction;
@@ -3859,7 +3881,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID &
TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR);
+ ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR);
MutexLock lock(sd->mutex);
return sd->para_direction;
@@ -3868,7 +3890,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(co
void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) {
_THREAD_SAFE_METHOD_
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
if (sd->custom_punct != p_punct) {
if (sd->parent != RID()) {
@@ -3882,13 +3904,13 @@ void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped
String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, String());
+ ERR_FAIL_NULL_V(sd, String());
return sd->custom_punct;
}
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_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->parent != RID()) {
@@ -3909,7 +3931,7 @@ void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, con
void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->orientation != p_orientation) {
@@ -3923,7 +3945,7 @@ void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextS
void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
ERR_FAIL_COND(sd->parent != RID());
@@ -3935,7 +3957,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped,
bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_invalid;
@@ -3943,7 +3965,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped)
void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->preserve_control != p_enabled) {
@@ -3957,7 +3979,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped,
bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_control;
@@ -3966,7 +3988,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped)
void TextServerAdvanced::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->extra_spacing[p_spacing] != p_value) {
@@ -3982,7 +4004,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
return sd->extra_spacing[p_spacing];
@@ -3990,7 +4012,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin
TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
+ ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL);
MutexLock lock(sd->mutex);
return sd->orientation;
@@ -3998,20 +4020,20 @@ TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const R
int64_t TextServerAdvanced::_shaped_get_span_count(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
return sd->spans.size();
}
Variant TextServerAdvanced::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Variant());
+ ERR_FAIL_NULL_V(sd, Variant());
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
return sd->spans[p_index].meta;
}
void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
ERR_FAIL_INDEX(p_index, sd->spans.size());
ShapedTextDataAdvanced::Span &span = sd->spans.ptrw()[p_index];
@@ -4024,7 +4046,7 @@ void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64
bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
ERR_FAIL_COND_V(p_size <= 0, false);
MutexLock lock(sd->mutex);
@@ -4060,7 +4082,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri
bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) {
_THREAD_SAFE_METHOD_
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
ERR_FAIL_COND_V(p_key == Variant(), false);
ERR_FAIL_COND_V(sd->objects.has(p_key), false);
@@ -4090,7 +4112,7 @@ bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Vari
bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), false);
@@ -4130,8 +4152,8 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V
} else {
if (gl.font_rid.is_valid()) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
- sd->ascent = MAX(sd->ascent, MAX(_font_get_ascent(gl.font_rid, gl.font_size), -gl.y_off));
- sd->descent = MAX(sd->descent, MAX(_font_get_descent(gl.font_rid, gl.font_size), gl.y_off));
+ sd->ascent = MAX(sd->ascent, MAX(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_TOP), -gl.y_off));
+ sd->descent = MAX(sd->descent, MAX(_font_get_descent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_BOTTOM), gl.y_off));
} else {
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));
@@ -4233,7 +4255,7 @@ void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const {
RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, RID());
+ ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
if (sd->parent != RID()) {
@@ -4385,8 +4407,8 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
} else {
if (gl.font_rid.is_valid()) {
if (p_new_sd->orientation == ORIENTATION_HORIZONTAL) {
- p_new_sd->ascent = MAX(p_new_sd->ascent, MAX(_font_get_ascent(gl.font_rid, gl.font_size), -gl.y_off));
- p_new_sd->descent = MAX(p_new_sd->descent, MAX(_font_get_descent(gl.font_rid, gl.font_size), gl.y_off));
+ p_new_sd->ascent = MAX(p_new_sd->ascent, MAX(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_TOP), -gl.y_off));
+ p_new_sd->descent = MAX(p_new_sd->descent, MAX(_font_get_descent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_BOTTOM), gl.y_off));
} else {
p_new_sd->ascent = MAX(p_new_sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
p_new_sd->descent = MAX(p_new_sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
@@ -4417,7 +4439,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, RID());
+ ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
return sd->parent;
@@ -4425,7 +4447,7 @@ RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const {
double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<TextServer::JustificationFlag> p_jst_flags) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4582,7 +4604,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4638,7 +4660,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
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_COND_MSG(!sd, "ShapedTextDataAdvanced invalid.");
+ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4708,7 +4730,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
- ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + (cut_per_word ? whitespace_adv.x : 0);
+ 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);
}
int ell_min_characters = 6;
@@ -4771,7 +4793,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for aesthetics.
+ // Insert an additional space when cutting word bound for esthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
@@ -4805,7 +4827,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.trim_pos;
@@ -4813,7 +4835,7 @@ int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const
int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_pos;
@@ -4821,7 +4843,7 @@ int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c
const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataAdvanced invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.ptr();
@@ -4829,15 +4851,84 @@ const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_s
int64_t TextServerAdvanced::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataAdvanced invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
}
+void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
+ if (!p_sd->chars_valid) {
+ p_sd->chars.clear();
+
+ const UChar *data = p_sd->utf16.get_data();
+ UErrorCode err = U_ZERO_ERROR;
+ int prev = -1;
+ int i = 0;
+
+ Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans;
+ if (p_sd->parent != RID()) {
+ ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent);
+ ERR_FAIL_COND(!parent_sd->valid);
+ spans = parent_sd->spans;
+ }
+
+ while (i < spans.size()) {
+ if (spans[i].start > p_sd->end) {
+ break;
+ }
+ if (spans[i].end < p_sd->start) {
+ i++;
+ continue;
+ }
+
+ int r_start = MAX(0, spans[i].start - p_sd->start);
+ String language = spans[i].language;
+ while (i + 1 < spans.size() && language == spans[i + 1].language) {
+ i++;
+ }
+ int r_end = MIN(spans[i].end - p_sd->start, p_sd->text.length());
+ UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, (language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale().ascii().get_data() : language.ascii().get_data(), data + _convert_pos_inv(p_sd, r_start), _convert_pos_inv(p_sd, r_end - r_start), &err);
+ if (U_SUCCESS(err)) {
+ while (ubrk_next(bi) != UBRK_DONE) {
+ int pos = _convert_pos(p_sd, ubrk_current(bi)) + r_start + p_sd->start;
+ if (prev != pos) {
+ p_sd->chars.push_back(pos);
+ }
+ prev = pos;
+ }
+ ubrk_close(bi);
+ } else {
+ for (int j = r_start; j < r_end; j++) {
+ if (prev != j) {
+ p_sd->chars.push_back(j + 1 + p_sd->start);
+ }
+ prev = j;
+ }
+ }
+ i++;
+ }
+ p_sd->chars_valid = true;
+ }
+}
+
+PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID &p_shaped) const {
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL_V(sd, PackedInt32Array());
+
+ MutexLock lock(sd->mutex);
+ if (!sd->valid) {
+ const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+ }
+
+ _update_chars(sd);
+
+ return sd->chars;
+}
+
bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -5102,7 +5193,7 @@ _FORCE_INLINE_ int64_t _generate_kashida_justification_opportunies(const String
bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shaped) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -5256,7 +5347,7 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char
hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size);
double scale = _font_get_scale(p_font, p_font_size);
bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
- ERR_FAIL_COND_V(hb_font == nullptr, Glyph());
+ ERR_FAIL_NULL_V(hb_font, Glyph());
hb_buffer_clear_contents(p_sd->hb_buffer);
hb_buffer_set_direction(p_sd->hb_buffer, p_direction);
@@ -5338,7 +5429,17 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
// Try system fallback.
RID fdef = p_fonts[0];
if (_font_is_allow_system_fallback(fdef)) {
- String text = p_sd->text.substr(p_start, 1);
+ _update_chars(p_sd);
+
+ int64_t next = p_end;
+ for (const int32_t &E : p_sd->chars) {
+ if (E > p_start) {
+ next = E;
+ 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);
@@ -5481,6 +5582,10 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
_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);
@@ -5529,18 +5634,18 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
}
FontAdvanced *fd = font_owner.get_or_null(f);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i fss = _get_size(fd, fs);
hb_font_t *hb_font = _font_get_hb_handle(f, fs);
double scale = _font_get_scale(f, fs);
- double sp_sp = p_sd->extra_spacing[SPACING_SPACE];
- double sp_gl = p_sd->extra_spacing[SPACING_GLYPH];
+ double sp_sp = p_sd->extra_spacing[SPACING_SPACE] + _font_get_spacing(f, SPACING_SPACE);
+ double sp_gl = p_sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(f, SPACING_GLYPH);
bool last_run = (p_sd->end == p_end);
double ea = _get_extra_advance(f, fs);
bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
- ERR_FAIL_COND(hb_font == nullptr);
+ ERR_FAIL_NULL(hb_font);
hb_buffer_clear_contents(p_sd->hb_buffer);
hb_buffer_set_direction(p_sd->hb_buffer, p_direction);
@@ -5721,8 +5826,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
if (failed_subrun_start != p_end + 1) {
_shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end);
}
- p_sd->ascent = MAX(p_sd->ascent, _font_get_ascent(f, fs));
- p_sd->descent = MAX(p_sd->descent, _font_get_descent(f, fs));
+ p_sd->ascent = MAX(p_sd->ascent, _font_get_ascent(f, fs) + _font_get_spacing(f, SPACING_TOP));
+ p_sd->descent = MAX(p_sd->descent, _font_get_descent(f, fs) + _font_get_spacing(f, SPACING_BOTTOM));
p_sd->upos = MAX(p_sd->upos, _font_get_underline_position(f, fs));
p_sd->uthk = MAX(p_sd->uthk, _font_get_underline_thickness(f, fs));
}
@@ -5731,7 +5836,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
_THREAD_SAFE_METHOD_
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (sd->valid) {
@@ -5777,8 +5882,11 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR;
sd->base_para_direction = direction;
} else {
- sd->para_direction = DIRECTION_LTR;
- sd->base_para_direction = UBIDI_DEFAULT_LTR;
+ const String &lang = (sd->spans.is_empty() || sd->spans[0].language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : sd->spans[0].language;
+ bool lang_rtl = _is_locale_right_to_left(lang);
+
+ sd->para_direction = lang_rtl ? DIRECTION_RTL : DIRECTION_LTR;
+ sd->base_para_direction = lang_rtl ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR;
}
} break;
}
@@ -5944,7 +6052,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->valid;
@@ -5952,7 +6060,7 @@ bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const {
const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, nullptr);
+ ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -5963,7 +6071,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co
int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -5974,7 +6082,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co
const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, nullptr);
+ ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -5992,7 +6100,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped)
Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Vector2i());
+ ERR_FAIL_NULL_V(sd, Vector2i());
MutexLock lock(sd->mutex);
return Vector2(sd->start, sd->end);
@@ -6001,7 +6109,7 @@ Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const {
Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const {
Array ret;
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, ret);
+ ERR_FAIL_NULL_V(sd, ret);
MutexLock lock(sd->mutex);
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
@@ -6013,7 +6121,7 @@ Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const {
Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Rect2());
+ ERR_FAIL_NULL_V(sd, Rect2());
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
@@ -6025,7 +6133,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons
Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Size2());
+ ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6040,7 +6148,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const {
double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6051,7 +6159,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const {
double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6062,7 +6170,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const {
double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6073,7 +6181,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const {
double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6085,7 +6193,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap
double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -6602,6 +6710,30 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str
return ret;
}
+PackedInt32Array TextServerAdvanced::_string_get_character_breaks(const String &p_string, const String &p_language) const {
+ const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
+ // Convert to UTF-16.
+ Char16String utf16 = p_string.utf16();
+
+ PackedInt32Array ret;
+
+ UErrorCode err = U_ZERO_ERROR;
+ UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err);
+ if (U_SUCCESS(err)) {
+ while (ubrk_next(bi) != UBRK_DONE) {
+ int pos = _convert_pos(p_string, utf16, ubrk_current(bi));
+ ret.push_back(pos);
+ }
+ ubrk_close(bi);
+ } else {
+ for (int i = 0; i <= p_string.size(); i++) {
+ ret.push_back(i);
+ }
+ }
+
+ return ret;
+}
+
bool TextServerAdvanced::_is_valid_identifier(const String &p_string) const {
#ifndef ICU_STATIC_DATA
if (!icu_data_loaded) {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 44700e045b..7445becfae 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -316,6 +316,7 @@ class TextServerAdvanced : public TextServerExtension {
String style_name;
int weight = 400;
int stretch = 100;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache;
@@ -509,9 +510,11 @@ class TextServerAdvanced : public TextServerExtension {
HashMap<int, bool> jstops;
HashMap<int, bool> breaks;
+ PackedInt32Array chars;
int break_inserts = 0;
bool break_ops_valid = false;
bool js_ops_valid = false;
+ bool chars_valid = false;
~ShapedTextDataAdvanced() {
for (int i = 0; i < bidi_iter.size(); i++) {
@@ -552,9 +555,10 @@ class TextServerAdvanced : public TextServerExtension {
double oversampling = 0.0;
double embolden = 0.0;
Transform2D transform;
+ int extra_spacing[4] = { 0, 0, 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);
+ 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]);
}
SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerAdvanced *p_fb) {
@@ -575,6 +579,10 @@ class TextServerAdvanced : public TextServerExtension {
oversampling = p_fb->_font_get_oversampling(p_font);
embolden = p_fb->_font_get_embolden(p_font);
transform = p_fb->_font_get_transform(p_font);
+ extra_spacing[SPACING_TOP] = p_fb->_font_get_spacing(p_font, SPACING_TOP);
+ 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);
}
};
@@ -603,12 +611,18 @@ class TextServerAdvanced : public TextServerExtension {
hash = hash_murmur3_one_real(p_a.transform[0].y, hash);
hash = hash_murmur3_one_real(p_a.transform[1].x, hash);
hash = hash_murmur3_one_real(p_a.transform[1].y, hash);
+ hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_TOP], hash);
+ 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);
+
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));
}
};
mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
mutable HashMap<String, PackedByteArray> system_font_data;
+ void _update_chars(ShapedTextDataAdvanced *p_sd) const;
void _realign(ShapedTextDataAdvanced *p_sd) const;
int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const;
int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const;
@@ -745,6 +759,9 @@ public:
MODBIND2(font_set_embolden, const RID &, double);
MODBIND1RC(double, font_get_embolden, const RID &);
+ MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t);
+ MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType);
+
MODBIND2(font_set_transform, const RID &, const Transform2D &);
MODBIND1RC(Transform2D, font_get_transform, const RID &);
@@ -920,11 +937,14 @@ public:
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
+ MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
+
MODBIND2RC(String, format_number, const String &, const String &);
MODBIND2RC(String, parse_number, const String &, const String &);
MODBIND1RC(String, percent_sign, const String &);
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
+ MODBIND2RC(PackedInt32Array, string_get_character_breaks, const String &, const String &);
MODBIND2RC(int64_t, is_confusable, const String &, const PackedStringArray &);
MODBIND1RC(bool, spoof_check, const String &);
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp
index 2f48f1564c..828f8d7d56 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp
@@ -155,21 +155,10 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
}
String xml_code_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
-#ifndef GDEXTENSION
gl_state.xml_code = xml_code_str.utf8();
-#else
- CharString xml_code = xml_code_str.utf8();
- gl_state.xml_code_length = xml_code.length();
- gl_state.xml_code = memnew_arr(char, gl_state.xml_code_length);
- memcpy(gl_state.xml_code, xml_code.get_data(), gl_state.xml_code_length);
-#endif
picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- result = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (result != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
}
@@ -198,8 +187,8 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
}
- gl_state.bmp_y = -min_y * gl_state.h / new_h;
- gl_state.bmp_x = min_x * gl_state.w / new_w;
+ gl_state.bmp_y = gl_state.h + metrics.descender / 64.f;
+ gl_state.bmp_x = 0;
gl_state.ready = true;
}
@@ -257,11 +246,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
tvg::Result res = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- tvg::Result res = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
}
@@ -271,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h
index 5e79c8e444..034fffb5e6 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.h
+++ b/modules/text_server_adv/thorvg_svg_in_ot.h
@@ -66,22 +66,8 @@ struct GL_State {
float y = 0;
float w = 0;
float h = 0;
-#ifndef GDEXTENSION
CharString xml_code;
-#else
- // @todo After godot-cpp#1142 is fixed, use CharString just like when compiled as a Godot module.
- char *xml_code = nullptr;
- int xml_code_length = 0;
-#endif
tvg::Matrix m;
-
-#ifdef GDEXTENSION
- ~GL_State() {
- if (xml_code) {
- memdelete_arr(xml_code);
- }
- }
-#endif
};
struct TVG_State {
diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub
index 582e622147..e808864512 100644
--- a/modules/text_server_fb/SCsub
+++ b/modules/text_server_fb/SCsub
@@ -9,7 +9,11 @@ msdfgen_enabled = "msdfgen" in env.module_list
env_text_server_fb = env_modules.Clone()
if "svg" in env.module_list:
- env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+ env_text_server_fb.Prepend(
+ CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/common", "#thirdparty/thorvg/src/renderer"]
+ )
+ # Enable ThorVG static object linking.
+ env_text_server_fb.Append(CPPDEFINES=["TVG_STATIC"])
if env["builtin_msdfgen"] and msdfgen_enabled:
# Treat msdfgen headers as system headers to avoid raising warnings. Not supported on MSVC.
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index 51a6ee06be..846ac02cf1 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -37,64 +37,69 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
thirdparty_tvg_sources = [
- "src/lib/sw_engine/tvgSwFill.cpp",
- "src/lib/sw_engine/tvgSwImage.cpp",
- "src/lib/sw_engine/tvgSwMath.cpp",
- "src/lib/sw_engine/tvgSwMemPool.cpp",
- "src/lib/sw_engine/tvgSwRaster.cpp",
- "src/lib/sw_engine/tvgSwRenderer.cpp",
- "src/lib/sw_engine/tvgSwRle.cpp",
- "src/lib/sw_engine/tvgSwShape.cpp",
- "src/lib/sw_engine/tvgSwStroke.cpp",
- "src/lib/tvgAccessor.cpp",
- "src/lib/tvgBezier.cpp",
- "src/lib/tvgCanvas.cpp",
- "src/lib/tvgFill.cpp",
- "src/lib/tvgGlCanvas.cpp",
- "src/lib/tvgInitializer.cpp",
- "src/lib/tvgLinearGradient.cpp",
- "src/lib/tvgLoader.cpp",
- "src/lib/tvgLzw.cpp",
- "src/lib/tvgPaint.cpp",
- "src/lib/tvgPicture.cpp",
- "src/lib/tvgRadialGradient.cpp",
- "src/lib/tvgRender.cpp",
- "src/lib/tvgSaver.cpp",
- "src/lib/tvgScene.cpp",
- "src/lib/tvgShape.cpp",
- "src/lib/tvgSwCanvas.cpp",
- "src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
- "src/loaders/raw/tvgRawLoader.cpp",
+ # common
+ "src/common/tvgBezier.cpp",
+ "src/common/tvgCompressor.cpp",
+ "src/common/tvgMath.cpp",
+ "src/common/tvgStr.cpp",
+ # SVG parser
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
"src/loaders/svg/tvgSvgPath.cpp",
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
+ "src/loaders/raw/tvgRawLoader.cpp",
+ # renderer common
+ "src/renderer/tvgAccessor.cpp",
+ # "src/renderer/tvgAnimation.cpp",
+ "src/renderer/tvgCanvas.cpp",
+ "src/renderer/tvgFill.cpp",
+ # "src/renderer/tvgGlCanvas.cpp",
+ "src/renderer/tvgInitializer.cpp",
+ "src/renderer/tvgLoader.cpp",
+ "src/renderer/tvgPaint.cpp",
+ "src/renderer/tvgPicture.cpp",
+ "src/renderer/tvgRender.cpp",
+ # "src/renderer/tvgSaver.cpp",
+ "src/renderer/tvgScene.cpp",
+ "src/renderer/tvgShape.cpp",
+ "src/renderer/tvgSwCanvas.cpp",
+ "src/renderer/tvgTaskScheduler.cpp",
+ # renderer sw_engine
+ "src/renderer/sw_engine/tvgSwFill.cpp",
+ "src/renderer/sw_engine/tvgSwImage.cpp",
+ "src/renderer/sw_engine/tvgSwMath.cpp",
+ "src/renderer/sw_engine/tvgSwMemPool.cpp",
+ "src/renderer/sw_engine/tvgSwRaster.cpp",
+ "src/renderer/sw_engine/tvgSwRenderer.cpp",
+ "src/renderer/sw_engine/tvgSwRle.cpp",
+ "src/renderer/sw_engine/tvgSwShape.cpp",
+ "src/renderer/sw_engine/tvgSwStroke.cpp",
]
thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
env_tvg.Append(
CPPPATH=[
"../../../thirdparty/thorvg/inc",
- "../../../thirdparty/thorvg/src/lib",
- "../../../thirdparty/thorvg/src/lib/sw_engine",
- "../../../thirdparty/thorvg/src/loaders/external_png",
- "../../../thirdparty/thorvg/src/loaders/jpg",
- "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/common",
"../../../thirdparty/thorvg/src/loaders/svg",
- "../../../thirdparty/thorvg/src/loaders/tvg",
- "../../../thirdparty/thorvg/src/savers/tvg",
- "../../../thirdparty/libpng",
+ "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/renderer",
+ "../../../thirdparty/thorvg/src/renderer/sw_engine",
+ ]
+ )
+
+ # Enable ThorVG static object linking.
+ env_tvg.Append(CPPDEFINES=["TVG_STATIC"])
+
+ env.Append(
+ CPPPATH=[
+ "../../../thirdparty/thorvg/inc",
+ "../../../thirdparty/thorvg/src/common",
+ "../../../thirdparty/thorvg/src/renderer",
]
)
- env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
lib = env_tvg.Library(
@@ -257,7 +262,6 @@ if env["freetype_enabled"]:
CPPDEFINES=[
"FT2_BUILD_LIBRARY",
"FT_CONFIG_OPTION_USE_PNG",
- ("PNG_ARM_NEON_OPT", 0),
"FT_CONFIG_OPTION_SYSTEM_ZLIB",
]
)
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index dc0a7df81a..c3b64929a9 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -937,7 +937,7 @@ RID TextServerFallback::_create_font() {
void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
@@ -948,7 +948,7 @@ void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteA
void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
@@ -959,7 +959,7 @@ void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t
void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -972,7 +972,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index >= 0x7FFF);
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->face_index != p_face_index) {
@@ -983,7 +983,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
return fd->face_index;
@@ -991,7 +991,7 @@ int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
int face_count = 0;
@@ -1037,7 +1037,7 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1047,7 +1047,7 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1057,7 +1057,7 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1067,7 +1067,7 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1077,7 +1077,7 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 400);
+ ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1087,7 +1087,7 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1097,7 +1097,7 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 100);
+ ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1107,7 +1107,7 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1117,7 +1117,7 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -1127,7 +1127,7 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->antialiasing != p_antialiasing) {
@@ -1138,7 +1138,7 @@ void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServe
TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE);
+ ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
return fd->antialiasing;
@@ -1146,7 +1146,7 @@ TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RI
void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->mipmaps != p_generate_mipmaps) {
@@ -1162,7 +1162,7 @@ void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool
bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->mipmaps;
@@ -1170,7 +1170,7 @@ bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const
void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf != p_msdf) {
@@ -1181,7 +1181,7 @@ void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID
bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf;
@@ -1189,7 +1189,7 @@ bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &
void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf_range != p_msdf_pixel_range) {
@@ -1200,7 +1200,7 @@ void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_range;
@@ -1208,7 +1208,7 @@ int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) co
void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->msdf_source_size != p_msdf_size) {
@@ -1219,7 +1219,7 @@ void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_source_size;
@@ -1227,7 +1227,7 @@ int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->fixed_size = p_fixed_size;
@@ -1235,7 +1235,7 @@ void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->fixed_size;
@@ -1243,7 +1243,7 @@ int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->allow_system_fallback = p_allow_system_fallback;
@@ -1251,7 +1251,7 @@ void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid,
bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->allow_system_fallback;
@@ -1259,7 +1259,7 @@ bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) c
void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->force_autohinter != p_force_autohinter) {
@@ -1270,7 +1270,7 @@ void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool
bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->force_autohinter;
@@ -1278,7 +1278,7 @@ bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const
void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->hinting != p_hinting) {
@@ -1289,7 +1289,7 @@ void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, HINTING_NONE);
+ ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
return fd->hinting;
@@ -1297,7 +1297,7 @@ TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid)
void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->subpixel_positioning = p_subpixel;
@@ -1305,7 +1305,7 @@ void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, T
TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED);
+ ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
return fd->subpixel_positioning;
@@ -1313,7 +1313,7 @@ TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioni
void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->embolden != p_strength) {
@@ -1324,15 +1324,37 @@ void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_stre
double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
return fd->embolden;
}
+void TextServerFallback::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
+ ERR_FAIL_INDEX((int)p_spacing, 4);
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ _font_clear_cache(fd);
+ fd->extra_spacing[p_spacing] = p_value;
+ }
+}
+
+int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
+ ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
+
+ MutexLock lock(fd->mutex);
+ return fd->extra_spacing[p_spacing];
+}
+
void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->transform != p_transform) {
@@ -1343,7 +1365,7 @@ void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transf
Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Transform2D());
+ ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
return fd->transform;
@@ -1351,18 +1373,18 @@ Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const
void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
- if (fd->variation_coordinates != p_variation_coordinates) {
+ if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) {
_font_clear_cache(fd);
- fd->variation_coordinates = p_variation_coordinates;
+ fd->variation_coordinates = p_variation_coordinates.duplicate();
}
}
Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->variation_coordinates;
@@ -1370,7 +1392,7 @@ Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font
void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
if (fd->oversampling != p_oversampling) {
@@ -1381,7 +1403,7 @@ void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_
double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
return fd->oversampling;
@@ -1389,7 +1411,7 @@ double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
+ ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
TypedArray<Vector2i> ret;
@@ -1401,7 +1423,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_
void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
@@ -1413,7 +1435,7 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
@@ -1425,7 +1447,7 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve
void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1436,7 +1458,7 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1452,7 +1474,7 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -1462,7 +1484,7 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1478,7 +1500,7 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1489,7 +1511,7 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1505,7 +1527,7 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1516,7 +1538,7 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1532,7 +1554,7 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1548,7 +1570,7 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0.0);
+ ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1564,7 +1586,7 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, 0);
+ ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1576,7 +1598,7 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1586,7 +1608,7 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1598,7 +1620,7 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
MutexLock lock(fd->mutex);
@@ -1627,7 +1649,7 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Ref<Image>());
+ ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1641,7 +1663,7 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1660,7 +1682,7 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedInt32Array());
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1685,7 +1707,7 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedInt32Array());
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1701,7 +1723,7 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1712,7 +1734,7 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1723,7 +1745,7 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1761,7 +1783,7 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -1776,7 +1798,7 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1806,7 +1828,7 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1821,7 +1843,7 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1851,7 +1873,7 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1866,7 +1888,7 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Rect2());
+ ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1891,7 +1913,7 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1906,7 +1928,7 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, -1);
+ ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1931,7 +1953,7 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1946,7 +1968,7 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, RID());
+ ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1992,7 +2014,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Size2());
+ ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2038,7 +2060,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2088,7 +2110,7 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
+ ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2104,7 +2126,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2115,7 +2137,7 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2126,7 +2148,7 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2137,7 +2159,7 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Vector2());
+ ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2202,7 +2224,7 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, String());
+ ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
if (fd->cache.is_empty()) {
@@ -2235,7 +2257,7 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -2270,7 +2292,7 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2301,7 +2323,7 @@ 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 {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
@@ -2393,7 +2415,7 @@ 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 {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
@@ -2485,7 +2507,7 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
if (fd->language_support_overrides.has(p_language)) {
@@ -2497,7 +2519,7 @@ bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, cons
void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides[p_language] = p_supported;
@@ -2505,7 +2527,7 @@ void TextServerFallback::_font_set_language_support_override(const RID &p_font_r
bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->language_support_overrides[p_language];
@@ -2513,7 +2535,7 @@ bool TextServerFallback::_font_get_language_support_override(const RID &p_font_r
void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides.erase(p_language);
@@ -2521,7 +2543,7 @@ void TextServerFallback::_font_remove_language_support_override(const RID &p_fon
PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedStringArray());
+ ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
@@ -2533,7 +2555,7 @@ PackedStringArray TextServerFallback::_font_get_language_support_overrides(const
bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
if (fd->script_support_overrides.has(p_script)) {
@@ -2545,7 +2567,7 @@ bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const
void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
fd->script_support_overrides[p_script] = p_supported;
@@ -2553,7 +2575,7 @@ void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid
bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, false);
+ ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
return fd->script_support_overrides[p_script];
@@ -2561,7 +2583,7 @@ bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid
void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2571,7 +2593,7 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, PackedStringArray());
+ ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
@@ -2583,7 +2605,7 @@ PackedStringArray TextServerFallback::_font_get_script_support_overrides(const R
void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND(!fd);
+ ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2593,7 +2615,7 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->feature_overrides;
@@ -2605,7 +2627,7 @@ Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_ri
Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_COND_V(!fd, Dictionary());
+ ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
@@ -2694,7 +2716,7 @@ RID TextServerFallback::_create_shaped_text(TextServer::Direction p_direction, T
void TextServerFallback::_shaped_text_clear(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
sd->parent = RID();
@@ -2724,7 +2746,7 @@ TextServer::Direction TextServerFallback::_shaped_text_get_inferred_direction(co
void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) {
_THREAD_SAFE_METHOD_
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
if (sd->custom_punct != p_punct) {
if (sd->parent != RID()) {
@@ -2738,13 +2760,13 @@ void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped
String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, String());
+ ERR_FAIL_NULL_V(sd, String());
return sd->custom_punct;
}
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_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->orientation != p_orientation) {
@@ -2762,7 +2784,7 @@ void TextServerFallback::_shaped_text_set_bidi_override(const RID &p_shaped, con
TextServer::Orientation TextServerFallback::_shaped_text_get_orientation(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
+ ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL);
MutexLock lock(sd->mutex);
return sd->orientation;
@@ -2772,7 +2794,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped,
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
MutexLock lock(sd->mutex);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
if (sd->preserve_invalid != p_enabled) {
if (sd->parent != RID()) {
full_copy(sd);
@@ -2784,7 +2806,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped,
bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_invalid;
@@ -2792,7 +2814,7 @@ bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped)
void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->preserve_control != p_enabled) {
@@ -2806,7 +2828,7 @@ void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped,
bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_control;
@@ -2815,7 +2837,7 @@ bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped)
void TextServerFallback::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
MutexLock lock(sd->mutex);
if (sd->extra_spacing[p_spacing] != p_value) {
@@ -2831,7 +2853,7 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
return sd->extra_spacing[p_spacing];
@@ -2839,20 +2861,20 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin
int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
return sd->spans.size();
}
Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Variant());
+ ERR_FAIL_NULL_V(sd, Variant());
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
return sd->spans[p_index].meta;
}
void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND(!sd);
+ ERR_FAIL_NULL(sd);
ERR_FAIL_INDEX(p_index, sd->spans.size());
ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index];
@@ -2876,7 +2898,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64
bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(p_size <= 0, false);
@@ -2927,7 +2949,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri
bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(p_key == Variant(), false);
@@ -2959,7 +2981,7 @@ bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Vari
bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), false);
@@ -2999,8 +3021,8 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V
} else {
if (gl.font_rid.is_valid()) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
- sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
- sd->descent = MAX(sd->descent, _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 {
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));
@@ -3103,7 +3125,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, RID());
+ ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
if (sd->parent != RID()) {
@@ -3165,8 +3187,8 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
} else {
if (gl.font_rid.is_valid()) {
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
- new_sd->ascent = MAX(new_sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
- new_sd->descent = MAX(new_sd->descent, _font_get_descent(gl.font_rid, gl.font_size));
+ new_sd->ascent = MAX(new_sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_TOP));
+ new_sd->descent = MAX(new_sd->descent, _font_get_descent(gl.font_rid, gl.font_size) + _font_get_spacing(gl.font_rid, SPACING_BOTTOM));
} else {
new_sd->ascent = MAX(new_sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
new_sd->descent = MAX(new_sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
@@ -3195,7 +3217,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, RID());
+ ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
return sd->parent;
@@ -3203,7 +3225,7 @@ RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const {
double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<JustificationFlag> p_jst_flags) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3312,7 +3334,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3368,7 +3390,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac
bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3424,7 +3446,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3440,7 +3462,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
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_COND_MSG(!sd, "ShapedTextDataFallback invalid.");
+ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3510,7 +3532,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
- ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + (cut_per_word ? whitespace_adv.x : 0);
+ 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);
}
int ell_min_characters = 6;
@@ -3563,7 +3585,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for aesthetics.
+ // Insert an additional space when cutting word bound for esthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
@@ -3597,7 +3619,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.trim_pos;
@@ -3605,7 +3627,7 @@ int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const
int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_pos;
@@ -3613,7 +3635,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c
const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.ptr();
@@ -3621,7 +3643,7 @@ const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_s
int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid.");
+ ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
@@ -3629,7 +3651,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_s
bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
if (sd->valid) {
@@ -3849,6 +3871,10 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
_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);
@@ -3873,8 +3899,8 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x;
gl.x_off = 0;
gl.y_off = 0;
- sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
- sd->descent = MAX(sd->descent, _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);
@@ -3886,9 +3912,9 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
if (j < sd->end - 1) {
// Do not add extra spacing to the last glyph of the string.
if (is_whitespace(sd->text[j - sd->start])) {
- gl.advance += sd->extra_spacing[SPACING_SPACE];
+ gl.advance += sd->extra_spacing[SPACING_SPACE] + _font_get_spacing(gl.font_rid, SPACING_SPACE);
} else {
- gl.advance += sd->extra_spacing[SPACING_GLYPH];
+ gl.advance += sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(gl.font_rid, SPACING_GLYPH);
}
}
sd->upos = MAX(sd->upos, _font_get_underline_position(gl.font_rid, gl.font_size));
@@ -3934,7 +3960,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->valid;
@@ -3942,7 +3968,7 @@ bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, nullptr);
+ ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3953,7 +3979,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co
int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0);
+ ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3964,7 +3990,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co
const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, nullptr);
+ ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -3976,7 +4002,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped)
Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Vector2i());
+ ERR_FAIL_NULL_V(sd, Vector2i());
MutexLock lock(sd->mutex);
return Vector2(sd->start, sd->end);
@@ -3985,7 +4011,7 @@ Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const {
Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const {
Array ret;
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, ret);
+ ERR_FAIL_NULL_V(sd, ret);
MutexLock lock(sd->mutex);
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
@@ -3997,7 +4023,7 @@ Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const {
Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Rect2());
+ ERR_FAIL_NULL_V(sd, Rect2());
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
@@ -4009,7 +4035,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons
Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, Size2());
+ ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4024,7 +4050,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4035,7 +4061,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4046,7 +4072,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4057,7 +4083,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4069,7 +4095,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap
double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V(!sd, 0.0);
+ ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
@@ -4079,6 +4105,26 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
return sd->uthk;
}
+PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID &p_shaped) const {
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_NULL_V(sd, PackedInt32Array());
+
+ MutexLock lock(sd->mutex);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+ }
+
+ PackedInt32Array ret;
+ int size = sd->end - sd->start;
+ if (size > 0) {
+ ret.resize(size);
+ for (int i = 0; i < size; i++) {
+ ret.write[i] = i + 1 + sd->start;
+ }
+ }
+ return ret;
+}
+
String TextServerFallback::_string_to_upper(const String &p_string, const String &p_language) const {
return p_string.to_upper();
}
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 54311caaf9..c44b45fc27 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -268,6 +268,7 @@ class TextServerFallback : public TextServerExtension {
String style_name;
int weight = 400;
int stretch = 100;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
@@ -471,9 +472,10 @@ class TextServerFallback : public TextServerExtension {
double oversampling = 0.0;
double embolden = 0.0;
Transform2D transform;
+ int extra_spacing[4] = { 0, 0, 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);
+ 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]);
}
SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) {
@@ -494,6 +496,10 @@ class TextServerFallback : public TextServerExtension {
oversampling = p_fb->_font_get_oversampling(p_font);
embolden = p_fb->_font_get_embolden(p_font);
transform = p_fb->_font_get_transform(p_font);
+ extra_spacing[SPACING_TOP] = p_fb->_font_get_spacing(p_font, SPACING_TOP);
+ 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);
}
};
@@ -522,6 +528,10 @@ class TextServerFallback : public TextServerExtension {
hash = hash_murmur3_one_real(p_a.transform[0].y, hash);
hash = hash_murmur3_one_real(p_a.transform[1].x, hash);
hash = hash_murmur3_one_real(p_a.transform[1].y, hash);
+ hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_TOP], hash);
+ 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);
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));
}
};
@@ -613,6 +623,9 @@ public:
MODBIND2(font_set_embolden, const RID &, double);
MODBIND1RC(double, font_get_embolden, const RID &);
+ MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t);
+ MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType);
+
MODBIND2(font_set_transform, const RID &, const Transform2D &);
MODBIND1RC(Transform2D, font_get_transform, const RID &);
@@ -788,6 +801,8 @@ public:
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
+ MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
+
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
MODBIND2RC(String, string_to_upper, const String &, const String &);
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp
index 773b103c01..7c8fedabc8 100644
--- a/modules/text_server_fb/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp
@@ -187,8 +187,8 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
}
- gl_state.bmp_y = -min_y * gl_state.h / new_h;
- gl_state.bmp_x = min_x * gl_state.w / new_w;
+ gl_state.bmp_y = gl_state.h + metrics.descender / 64.f;
+ gl_state.bmp_x = 0;
gl_state.ready = true;
}
@@ -256,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index df7672754b..aef4f394b2 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -242,14 +242,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const {
}
void UPNP::add_device(Ref<UPNPDevice> device) {
- ERR_FAIL_COND(device == nullptr);
+ ERR_FAIL_NULL(device);
devices.push_back(device);
}
void UPNP::set_device(int index, Ref<UPNPDevice> device) {
ERR_FAIL_INDEX(index, devices.size());
- ERR_FAIL_COND(device == nullptr);
+ ERR_FAIL_NULL(device);
devices.set(index, device);
}
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index b54335b724..b155dfc5d9 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -328,9 +328,9 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) {
int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample);
if (samples_to_burn > samples_in_page) {
- WARN_PRINT("Burning more samples than we have in this page. Check seek algorithm.");
+ WARN_PRINT_ONCE("Burning more samples than we have in this page. Check seek algorithm.");
} else if (samples_to_burn < 0) {
- WARN_PRINT("Burning negative samples doesn't make sense. Check seek algorithm.");
+ WARN_PRINT_ONCE("Burning negative samples doesn't make sense. Check seek algorithm.");
}
// Seek again, this time we'll burn a specific number of samples instead of all of them.
diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
index c2dcb832e0..dd6c181eae 100644
--- a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
+++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
@@ -9,7 +9,7 @@
Ogg Vorbis requires more CPU to decode than [ResourceImporterWAV]. If you need to play a lot of simultaneous sounds, it's recommended to use WAV for those sounds instead, especially if targeting low-end devices.
</description>
<tutorials>
- <link title="Importing audio samples">https://docs.godotengine.org/en/latest/tutorials/assets_pipeline/importing_audio_samples.html</link>
+ <link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<methods>
<method name="load_from_buffer" qualifiers="static">
diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
index f8c015dd06..454f8f2ed4 100644
--- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
+++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
A WebRTC connection between the local computer and a remote peer. Provides an interface to connect, maintain and monitor the connection.
- Setting up a WebRTC connection between two peers from now on) may not seem a trivial task, but it can be broken down into 3 main steps:
+ Setting up a WebRTC connection between two peers may not seem a trivial task, but it can be broken down into 3 main steps:
- The peer that wants to initiate the connection ([code]A[/code] from now on) creates an offer and send it to the other peer ([code]B[/code] from now on).
- [code]B[/code] receives the offer, generate and answer, and sends it to [code]A[/code]).
- [code]A[/code] and [code]B[/code] then generates and exchange ICE candidates with each other.
diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp
index 687c7b711e..28ce36f1e8 100644
--- a/modules/webrtc/register_types.cpp
+++ b/modules/webrtc/register_types.cpp
@@ -42,11 +42,7 @@ void initialize_webrtc_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
-
-#define SET_HINT(NAME, _VAL_, _MAX_) \
- GLOBAL_DEF(PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater"), _VAL_);
-
- SET_HINT(WRTC_IN_BUF, 64, 4096);
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/webrtc/max_channel_in_buffer_kb", PROPERTY_HINT_RANGE, "2,4096,1,or_greater"), 64);
ClassDB::register_custom_instance_class<WebRTCPeerConnection>();
GDREGISTER_CLASS(WebRTCPeerConnectionExtension);
@@ -55,8 +51,6 @@ void initialize_webrtc_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(WebRTCDataChannelExtension);
GDREGISTER_CLASS(WebRTCMultiplayerPeer);
-
-#undef SET_HINT
}
void uninitialize_webrtc_module(ModuleInitializationLevel p_level) {
diff --git a/modules/webrtc/webrtc_data_channel.cpp b/modules/webrtc/webrtc_data_channel.cpp
index bebf5c2741..6c0d0bea37 100644
--- a/modules/webrtc/webrtc_data_channel.cpp
+++ b/modules/webrtc/webrtc_data_channel.cpp
@@ -61,7 +61,7 @@ void WebRTCDataChannel::_bind_methods() {
}
WebRTCDataChannel::WebRTCDataChannel() {
- _in_buffer_shift = nearest_shift((int)GLOBAL_GET(WRTC_IN_BUF) - 1) + 10;
+ _in_buffer_shift = nearest_shift((int)GLOBAL_GET("network/limits/webrtc/max_channel_in_buffer_kb") - 1) + 10;
}
WebRTCDataChannel::~WebRTCDataChannel() {
diff --git a/modules/webrtc/webrtc_data_channel.h b/modules/webrtc/webrtc_data_channel.h
index e884c8425d..f35461a5a0 100644
--- a/modules/webrtc/webrtc_data_channel.h
+++ b/modules/webrtc/webrtc_data_channel.h
@@ -33,8 +33,6 @@
#include "core/io/packet_peer.h"
-#define WRTC_IN_BUF PNAME("network/limits/webrtc/max_channel_in_buffer_kb")
-
class WebRTCDataChannel : public PacketPeer {
GDCLASS(WebRTCDataChannel, PacketPeer);
diff --git a/modules/webrtc/webrtc_data_channel_extension.h b/modules/webrtc/webrtc_data_channel_extension.h
index 462e089592..b7afbaf13a 100644
--- a/modules/webrtc/webrtc_data_channel_extension.h
+++ b/modules/webrtc/webrtc_data_channel_extension.h
@@ -35,7 +35,6 @@
#include "core/extension/ext_wrappers.gen.inc"
#include "core/object/gdvirtual.gen.inc"
-#include "core/object/script_language.h"
#include "core/variant/native_ptr.h"
class WebRTCDataChannelExtension : public WebRTCDataChannel {
diff --git a/modules/webrtc/webrtc_peer_connection_extension.h b/modules/webrtc/webrtc_peer_connection_extension.h
index f3339f1eb4..05d88e0f65 100644
--- a/modules/webrtc/webrtc_peer_connection_extension.h
+++ b/modules/webrtc/webrtc_peer_connection_extension.h
@@ -35,7 +35,6 @@
#include "core/extension/ext_wrappers.gen.inc"
#include "core/object/gdvirtual.gen.inc"
-#include "core/object/script_language.h"
#include "core/variant/native_ptr.h"
class WebRTCPeerConnectionExtension : public WebRTCPeerConnection {
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub
index 3f834471e5..8b469fe5be 100644
--- a/modules/websocket/SCsub
+++ b/modules/websocket/SCsub
@@ -25,7 +25,7 @@ elif env["builtin_wslay"]:
env_ws.Prepend(CPPPATH=[thirdparty_dir])
env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"])
- if env["platform"] == "windows" or env["platform"] == "uwp":
+ if env["platform"] == "windows":
env_ws.Append(CPPDEFINES=["HAVE_WINSOCK2_H"])
else:
env_ws.Append(CPPDEFINES=["HAVE_NETINET_IN_H"])
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index a127a6b75a..38cb614847 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -600,7 +600,7 @@ ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *
}
int WSLPeer::_wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) {
- ERR_FAIL_COND_V(!_static_rng, WSLAY_ERR_CALLBACK_FAILURE);
+ ERR_FAIL_NULL_V(_static_rng, WSLAY_ERR_CALLBACK_FAILURE);
Error err = _static_rng->get_random_bytes(buf, len);
ERR_FAIL_COND_V(err != OK, WSLAY_ERR_CALLBACK_FAILURE);
return 0;
@@ -676,7 +676,7 @@ void WSLPeer::poll() {
}
if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) {
- ERR_FAIL_COND(!wsl_ctx);
+ ERR_FAIL_NULL(wsl_ctx);
int err = 0;
if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) {
// Error close.
diff --git a/modules/zip/doc_classes/ZIPReader.xml b/modules/zip/doc_classes/ZIPReader.xml
index 0fa2672044..bf596806b2 100644
--- a/modules/zip/doc_classes/ZIPReader.xml
+++ b/modules/zip/doc_classes/ZIPReader.xml
@@ -25,6 +25,15 @@
Closes the underlying resources used by this instance.
</description>
</method>
+ <method name="file_exists">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="case_sensitive" type="bool" default="true" />
+ <description>
+ Returns [code]true[/code] if the file exists in the loaded zip archive.
+ Must be called after [method open].
+ </description>
+ </method>
<method name="get_files">
<return type="PackedStringArray" />
<description>
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index c8b4fb4e77..5f623476fc 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -33,7 +33,7 @@
#include "core/io/zip_io.h"
#include "core/os/os.h"
-Error ZIPPacker::open(String p_path, ZipAppend p_append) {
+Error ZIPPacker::open(const String &p_path, ZipAppend p_append) {
if (fa.is_valid()) {
close();
}
@@ -55,7 +55,7 @@ Error ZIPPacker::close() {
return err;
}
-Error ZIPPacker::start_file(String p_path) {
+Error ZIPPacker::start_file(const String &p_path) {
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
zip_fileinfo zipfi;
@@ -76,7 +76,7 @@ Error ZIPPacker::start_file(String p_path) {
return err == ZIP_OK ? OK : FAILED;
}
-Error ZIPPacker::write_file(Vector<uint8_t> p_data) {
+Error ZIPPacker::write_file(const Vector<uint8_t> &p_data) {
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
return zipWriteInFileInZip(zf, p_data.ptr(), p_data.size()) == ZIP_OK ? OK : FAILED;
diff --git a/modules/zip/zip_packer.h b/modules/zip/zip_packer.h
index 142d0fddbf..e194f5ebbe 100644
--- a/modules/zip/zip_packer.h
+++ b/modules/zip/zip_packer.h
@@ -52,11 +52,11 @@ public:
APPEND_ADDINZIP = 2,
};
- Error open(String p_path, ZipAppend p_append);
+ Error open(const String &p_path, ZipAppend p_append);
Error close();
- Error start_file(String p_path);
- Error write_file(Vector<uint8_t> p_data);
+ Error start_file(const String &p_path);
+ Error write_file(const Vector<uint8_t> &p_data);
Error close_file();
ZIPPacker();
diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp
index 2684875e1c..f4a92dce5b 100644
--- a/modules/zip/zip_reader.cpp
+++ b/modules/zip/zip_reader.cpp
@@ -33,7 +33,7 @@
#include "core/error/error_macros.h"
#include "core/io/zip_io.h"
-Error ZIPReader::open(String p_path) {
+Error ZIPReader::open(const String &p_path) {
if (fa.is_valid()) {
close();
}
@@ -81,7 +81,7 @@ PackedStringArray ZIPReader::get_files() {
return arr;
}
-PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) {
+PackedByteArray ZIPReader::read_file(const String &p_path, bool p_case_sensitive) {
ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "ZIPReader must be opened before use.");
int err = UNZ_OK;
@@ -118,6 +118,21 @@ PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) {
return data;
}
+bool ZIPReader::file_exists(const String &p_path, bool p_case_sensitive) {
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "ZIPReader must be opened before use.");
+
+ int cs = p_case_sensitive ? 1 : 2;
+ if (unzLocateFile(uzf, p_path.utf8().get_data(), cs) != UNZ_OK) {
+ return false;
+ }
+ if (unzOpenCurrentFile(uzf) != UNZ_OK) {
+ return false;
+ }
+
+ unzCloseCurrentFile(uzf);
+ return true;
+}
+
ZIPReader::ZIPReader() {}
ZIPReader::~ZIPReader() {
@@ -131,4 +146,5 @@ void ZIPReader::_bind_methods() {
ClassDB::bind_method(D_METHOD("close"), &ZIPReader::close);
ClassDB::bind_method(D_METHOD("get_files"), &ZIPReader::get_files);
ClassDB::bind_method(D_METHOD("read_file", "path", "case_sensitive"), &ZIPReader::read_file, DEFVAL(Variant(true)));
+ ClassDB::bind_method(D_METHOD("file_exists", "path", "case_sensitive"), &ZIPReader::file_exists, DEFVAL(Variant(true)));
}
diff --git a/modules/zip/zip_reader.h b/modules/zip/zip_reader.h
index c074197eb4..874bd81ed3 100644
--- a/modules/zip/zip_reader.h
+++ b/modules/zip/zip_reader.h
@@ -46,11 +46,12 @@ protected:
static void _bind_methods();
public:
- Error open(String p_path);
+ Error open(const String &p_path);
Error close();
PackedStringArray get_files();
- PackedByteArray read_file(String p_path, bool p_case_sensitive);
+ PackedByteArray read_file(const String &p_path, bool p_case_sensitive);
+ bool file_exists(const String &p_path, bool p_case_sensitive);
ZIPReader();
~ZIPReader();